Compare commits
26 Commits
67c048a11d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b801552839 | |||
| 3169055646 | |||
| 5d2e1918d1 | |||
| 4407fdc6b6 | |||
| b01c5cd410 | |||
| 63b3704e89 | |||
| f78a4912a6 | |||
| e25bcdc86e | |||
| 5143b45610 | |||
| f4b629df52 | |||
| 2ba8594e6b | |||
| 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
@@ -13,11 +13,19 @@ import {
|
|||||||
LogView,
|
LogView,
|
||||||
} from './pages/UserManage';
|
} from './pages/UserManage';
|
||||||
import { EconomicIndex, UserIndex } from './pages/IndexManage';
|
import { EconomicIndex, UserIndex } from './pages/IndexManage';
|
||||||
import { LandInfoView, CryptView, GameLogView, UserView, BusinessLogView, } from './pages/DataManage';
|
import {
|
||||||
|
LandInfoView,
|
||||||
|
GameLogView,
|
||||||
|
UserView,
|
||||||
|
BusinessLogView,
|
||||||
|
MetaItemView,
|
||||||
|
RankManage,
|
||||||
|
MetaCraftingView,
|
||||||
|
} from './pages/DataManage';
|
||||||
import {
|
import {
|
||||||
Board,
|
Board,
|
||||||
Event,
|
RewardEvent,
|
||||||
EventRegist,
|
RewardEventRegist,
|
||||||
Items,
|
Items,
|
||||||
Mail,
|
Mail,
|
||||||
MailRegist,
|
MailRegist,
|
||||||
@@ -26,7 +34,8 @@ import {
|
|||||||
UserBlockRegist,
|
UserBlockRegist,
|
||||||
LandAuction,
|
LandAuction,
|
||||||
BattleEvent,
|
BattleEvent,
|
||||||
MenuBanner, MenuBannerRegist,
|
MenuBanner, MenuBannerRegist, Ranking,
|
||||||
|
Event
|
||||||
} from './pages/ServiceManage';
|
} from './pages/ServiceManage';
|
||||||
|
|
||||||
const RouteInfo = () => {
|
const RouteInfo = () => {
|
||||||
@@ -59,8 +68,10 @@ const RouteInfo = () => {
|
|||||||
<Route path="userview" element={<UserView />} />
|
<Route path="userview" element={<UserView />} />
|
||||||
<Route path="landview" element={<LandInfoView />} />
|
<Route path="landview" element={<LandInfoView />} />
|
||||||
<Route path="gamelogview" element={<GameLogView />} />
|
<Route path="gamelogview" element={<GameLogView />} />
|
||||||
<Route path="cryptview" element={<CryptView />} />
|
|
||||||
<Route path="businesslogview" element={<BusinessLogView />} />
|
<Route path="businesslogview" element={<BusinessLogView />} />
|
||||||
|
<Route path="itemdictionary" element={<MetaItemView />} />
|
||||||
|
<Route path="craftdictionary" element={<MetaCraftingView />} />
|
||||||
|
<Route path="rankmanage" element={<RankManage />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/servicemanage">
|
<Route path="/servicemanage">
|
||||||
<Route path="board" element={<Board />} />
|
<Route path="board" element={<Board />} />
|
||||||
@@ -70,12 +81,14 @@ const RouteInfo = () => {
|
|||||||
<Route path="userblock/userblockregist" element={<UserBlockRegist />} />
|
<Route path="userblock/userblockregist" element={<UserBlockRegist />} />
|
||||||
<Route path="reportlist" element={<ReportList />} />
|
<Route path="reportlist" element={<ReportList />} />
|
||||||
<Route path="items" element={<Items />} />
|
<Route path="items" element={<Items />} />
|
||||||
<Route path="event" element={<Event />} />
|
<Route path="rewardevent" element={<RewardEvent />} />
|
||||||
<Route path="event/eventregist" element={<EventRegist />} />
|
<Route path="rewardevent/eventregist" element={<RewardEventRegist />} />
|
||||||
<Route path="landauction" element={<LandAuction />} />
|
<Route path="landauction" element={<LandAuction />} />
|
||||||
<Route path="battleevent" element={<BattleEvent />} />
|
<Route path="battleevent" element={<BattleEvent />} />
|
||||||
<Route path="menubanner" element={<MenuBanner />} />
|
<Route path="menubanner" element={<MenuBanner />} />
|
||||||
<Route path="menubanner/menubannerregist" element={<MenuBannerRegist />} />
|
<Route path="menubanner/menubannerregist" element={<MenuBannerRegist />} />
|
||||||
|
<Route path="ranking" element={<Ranking />} />
|
||||||
|
<Route path="event" element={<Event />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
95
src/apis/Dictionary.js
Normal file
95
src/apis/Dictionary.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
//운영 정보 관리 - 백과사전 api 연결
|
||||||
|
|
||||||
|
import { Axios, responseFileDownload } from '../utils';
|
||||||
|
|
||||||
|
// 아이템 백과사전 조회
|
||||||
|
export const getItemDictionaryList = async (token, searchType, searchData, largeType, smallType, brand, gender, order, size, currentPage) => {
|
||||||
|
try {
|
||||||
|
const response = await Axios.get(`/api/v1/dictionary/item/list?search_type=${searchType}&search_data=${searchData}
|
||||||
|
&large_type=${largeType}&small_type=${smallType}&brand=${brand}&gender=${gender}
|
||||||
|
&orderby=${order}&page_no=${currentPage}&page_size=${size}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('getItemDictionaryList API error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ItemDictionaryExport = async (token, params) => {
|
||||||
|
try {
|
||||||
|
await Axios.get(`/api/v1/dictionary/item/excel-export?search_type=${params.search_type}&search_data=${params.search_data}
|
||||||
|
&large_type=${params.large_type}&small_type=${params.small_type}&brand=${params.brand}&gender=${params.gender}
|
||||||
|
&lang=${params.lang}&task_id=${params.taskId}`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
responseType: 'blob'
|
||||||
|
}).then(response => {
|
||||||
|
responseFileDownload(response, {
|
||||||
|
defaultFileName: 'itemDictionary'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('ItemDictionaryExport Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCraftingDictionaryList = async (token, searchType, searchData, smallType, recipeType, order, size, currentPage) => {
|
||||||
|
try {
|
||||||
|
const response = await Axios.get(`/api/v1/dictionary/craft/list?search_type=${searchType}&search_data=${searchData}
|
||||||
|
&small_type=${smallType}&recipe_type=${recipeType}
|
||||||
|
&orderby=${order}&page_no=${currentPage}&page_size=${size}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('getCraftingDictionaryList API error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CraftingDictionaryExport = async (token, params) => {
|
||||||
|
try {
|
||||||
|
await Axios.get(`/api/v1/dictionary/craft/excel-export?search_type=${params.search_type}&search_data=${params.search_data}
|
||||||
|
&small_type=${params.small_type}&recipe_type=${params.recipe_type}
|
||||||
|
&lang=${params.lang}&task_id=${params.taskId}`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
responseType: 'blob'
|
||||||
|
}).then(response => {
|
||||||
|
responseFileDownload(response, {
|
||||||
|
defaultFileName: 'craftingDictionary'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('CraftingDictionaryExport Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BrandView = async (token) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.get(
|
||||||
|
`/api/v1/dictionary/brand/list`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.data.data.brand_list;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('BrandView Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
//운영서비스 관리 - 이벤트 api 연결
|
//운영서비스 관리 - 통합 이벤트 api 연결
|
||||||
|
|
||||||
import { Axios } from '../utils';
|
import { Axios } from '../utils';
|
||||||
|
|
||||||
// 이벤트 리스트 조회
|
// 이벤트 리스트 조회
|
||||||
export const EventView = async (token, title, content, status, startDate, endDate, order, size, currentPage) => {
|
export const EventView = async (token, searchData, status, startDate, endDate, order, size, currentPage) => {
|
||||||
try {
|
try {
|
||||||
const res = await Axios.get(
|
const res = await Axios.get(
|
||||||
`/api/v1/event/list?title=${title}&content=${content}&status=${status}&start_dt=${startDate}&end_dt=${endDate}&orderby=${order}&page_no=${currentPage}
|
`/api/v1/world-event/list?search_data=${searchData}&status=${status}&start_dt=${startDate}&end_dt=${endDate}
|
||||||
&page_size=${size}`,
|
&orderby=${order}&page_no=${currentPage}&page_size=${size}`,
|
||||||
{
|
{
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
},
|
},
|
||||||
@@ -24,11 +24,11 @@ export const EventView = async (token, title, content, status, startDate, endDat
|
|||||||
// 이벤트 상세보기
|
// 이벤트 상세보기
|
||||||
export const EventDetailView = async (token, id) => {
|
export const EventDetailView = async (token, id) => {
|
||||||
try {
|
try {
|
||||||
const res = await Axios.get(`/api/v1/event/detail/${id}`, {
|
const res = await Axios.get(`/api/v1/world-event/detail/${id}`, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.data.data.detail;
|
return res.data.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
throw new Error('EventDetailView Error', e);
|
throw new Error('EventDetailView Error', e);
|
||||||
@@ -39,11 +39,11 @@ export const EventDetailView = async (token, id) => {
|
|||||||
// 이벤트 등록
|
// 이벤트 등록
|
||||||
export const EventSingleRegist = async (token, params) => {
|
export const EventSingleRegist = async (token, params) => {
|
||||||
try {
|
try {
|
||||||
const res = await Axios.post(`/api/v1/event`, params, {
|
const res = await Axios.post(`/api/v1/world-event`, params, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
});
|
});
|
||||||
|
|
||||||
return res;
|
return res.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
throw new Error('EventSingleRegist Error', e);
|
throw new Error('EventSingleRegist Error', e);
|
||||||
@@ -51,10 +51,10 @@ export const EventSingleRegist = async (token, params) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 우편 수정
|
// 이벤트 수정
|
||||||
export const EventModify = async (token, id, params) => {
|
export const EventModify = async (token, id, params) => {
|
||||||
try {
|
try {
|
||||||
const res = await Axios.put(`/api/v1/event/${id}`, params, {
|
const res = await Axios.put(`/api/v1/world-event/${id}`, params, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,15 +66,14 @@ export const EventModify = async (token, id, params) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 우편 삭제
|
// 이벤트 삭제
|
||||||
export const EventDelete = async (token, params, id) => {
|
export const EventDelete = async (token, id) => {
|
||||||
try {
|
try {
|
||||||
const res = await Axios.delete(`/api/v1/event/delete`, {
|
const res = await Axios.delete(`/api/v1/world-event/delete?id=${id}`, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
data: { list: params },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.data.data.list;
|
return res.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
throw new Error('EventDelete Error', e);
|
throw new Error('EventDelete Error', e);
|
||||||
@@ -82,17 +81,20 @@ export const EventDelete = async (token, params, id) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 이벤트 우편 아이템 확인
|
// 이벤트 메타데이터 조회
|
||||||
export const EventIsItem = async (token, params) => {
|
export const EventActionView = async (token) => {
|
||||||
try {
|
try {
|
||||||
const res = await Axios.post(`/api/v1/event/item`, params, {
|
const res = await Axios.get(
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
`/api/v1/dictionary/event-action/list`,
|
||||||
});
|
{
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return res;
|
return res.data.data.event_action_list;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
throw new Error('EventIsItem Error', e);
|
throw new Error('EventActionView Error', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -12,7 +12,7 @@ export const LogViewList = async (token, searchType, searchKey, historyType, sta
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.data.data;
|
return res.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
throw new Error('LogViewList Error', e);
|
throw new Error('LogViewList Error', e);
|
||||||
|
|||||||
@@ -36,6 +36,19 @@ export const userTotalIndex = async token => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const dashboardCaliumIndex = async token => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.get(`/api/v1/indicators/dashboard/calium/converter`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
return res.data.data;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('dashboardCaliumIndex', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 유저 지표 다운로드
|
// 유저 지표 다운로드
|
||||||
export const userIndexExport = async (token, filename, sendDate, endDate) => {
|
export const userIndexExport = async (token, filename, sendDate, endDate) => {
|
||||||
try {
|
try {
|
||||||
@@ -62,10 +75,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;
|
||||||
@@ -183,10 +200,10 @@ export const PlaytimeIndexExport = async (token, filename, sendDate, endDate) =>
|
|||||||
|
|
||||||
// 2. 경제 지표
|
// 2. 경제 지표
|
||||||
|
|
||||||
// 재화 조회 (currency)
|
// 재화 획득 조회
|
||||||
export const CurrencyIndexView = async (token, start_dt, end_dt, currency_type) => {
|
export const CurrencyAcquireIndexView = async (token, start_dt, end_dt, currencyType, deltaType) => {
|
||||||
try {
|
try {
|
||||||
const res = await Axios.get(`/api/v1/indicators/currency/use?start_dt=${start_dt}&end_dt=${end_dt}¤cy_type=${currency_type}`, {
|
const res = await Axios.get(`/api/v1/indicators/currency/list?start_dt=${start_dt}&end_dt=${end_dt}¤cy_type=${currencyType}&delta_type=${deltaType}`, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -198,75 +215,10 @@ export const CurrencyIndexView = async (token, start_dt, end_dt, currency_type)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 재화 지표 다운로드
|
|
||||||
export const CurrencyIndexExport = async (token, filename, sendDate, endDate, currencyType) => {
|
|
||||||
try {
|
|
||||||
await Axios.get(`/api/v1/indicators/currency/excel-down?file=${filename}&start_dt=${sendDate}&end_dt=${endDate}¤cy_type=${currencyType}`, {
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
responseType: 'blob',
|
|
||||||
}).then(response => {
|
|
||||||
const href = URL.createObjectURL(response.data);
|
|
||||||
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = href;
|
|
||||||
link.setAttribute('download', `${filename}`);
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
|
|
||||||
document.body.removeChild(link);
|
|
||||||
URL.revokeObjectURL(href);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
throw new Error('CurrencyIndexExport Error', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// VBP
|
|
||||||
export const VbpIndexView = async (token, start_dt, end_dt) => {
|
|
||||||
try {
|
|
||||||
const res = await Axios.get(`/api/v1/indicators/currency/vbp?start_dt=${start_dt}&end_dt=${end_dt}`, {
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.data.data;
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
throw new Error('VbpIndexView Error', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// VBP 다운로드
|
|
||||||
export const VBPIndexExport = async (token, filename, sendDate, endDate) => {
|
|
||||||
try {
|
|
||||||
await Axios.get(`/api/v1/indicators/currency/excel-down?file=${filename}&start_dt=${sendDate}&end_dt=${endDate}`, {
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
responseType: 'blob',
|
|
||||||
}).then(response => {
|
|
||||||
const href = URL.createObjectURL(response.data);
|
|
||||||
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = href;
|
|
||||||
link.setAttribute('download', `${filename}`);
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
|
|
||||||
document.body.removeChild(link);
|
|
||||||
URL.revokeObjectURL(href);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
throw new Error('VBPIndexExport Error', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Item
|
// Item
|
||||||
export const ItemIndexView = async (token, start_dt, end_dt) => {
|
export const ItemIndexView = async (token, start_dt, end_dt, itemId, deltaType) => {
|
||||||
try {
|
try {
|
||||||
const res = await Axios.get(`/api/v1/indicators/currency/item?start_dt=${start_dt}&end_dt=${end_dt}`, {
|
const res = await Axios.get(`/api/v1/indicators/item/list?start_dt=${start_dt}&end_dt=${end_dt}&item_id=${itemId}&delta_type=${deltaType}`, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -278,27 +230,17 @@ export const ItemIndexView = async (token, start_dt, end_dt) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Item 다운로드
|
// Assets
|
||||||
export const ItemIndexExport = async (token, filename, sendDate, endDate) => {
|
export const AssetsIndexView = async (token, start_dt, end_dt, itemId, deltaType) => {
|
||||||
try {
|
try {
|
||||||
await Axios.get(`/api/v1/indicators/currency/excel-down?file=${filename}&start_dt=${sendDate}&end_dt=${endDate}`, {
|
const res = await Axios.get(`/api/v1/indicators/assets/list?start_dt=${start_dt}&end_dt=${end_dt}`, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
responseType: 'blob',
|
|
||||||
}).then(response => {
|
|
||||||
const href = URL.createObjectURL(response.data);
|
|
||||||
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = href;
|
|
||||||
link.setAttribute('download', `${filename}`);
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
|
|
||||||
document.body.removeChild(link);
|
|
||||||
URL.revokeObjectURL(href);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return res.data.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
throw new Error('ItemIndexExport Error', e);
|
throw new Error('AssetsIndexView Error', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -320,137 +262,4 @@ export const InstanceIndexView = async (token, data, start_dt, end_dt) => {
|
|||||||
throw new Error('InstanceIndexView Error', e);
|
throw new Error('InstanceIndexView Error', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Instance 다운로드
|
|
||||||
export const InstanceIndexExport = async (token, filename, data, sendDate, endDate) => {
|
|
||||||
try {
|
|
||||||
await Axios.get(
|
|
||||||
`/api/v1/indicators/currency/excel-down?file=${filename}&search_key=${data ? data : ''}
|
|
||||||
&start_dt=${sendDate}&end_dt=${endDate}`,
|
|
||||||
{
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
responseType: 'blob',
|
|
||||||
},
|
|
||||||
).then(response => {
|
|
||||||
const href = URL.createObjectURL(response.data);
|
|
||||||
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = href;
|
|
||||||
link.setAttribute('download', `${filename}`);
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
|
|
||||||
document.body.removeChild(link);
|
|
||||||
URL.revokeObjectURL(href);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
throw new Error('InstanceIndexExport Error', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Clothes
|
|
||||||
export const ClothesIndexView = async (token, data, start_dt, end_dt) => {
|
|
||||||
try {
|
|
||||||
const res = await Axios.get(`/api/v1/indicators/currency/clothes?search_key=${data ? data : ''}&start_dt=${start_dt}&end_dt=${end_dt}`, {
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.data.data;
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
throw new Error('ClothesIndexView Error', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Clothes 다운로드
|
|
||||||
export const ClothesIndexExport = async (token, filename, data, sendDate, endDate) => {
|
|
||||||
try {
|
|
||||||
await Axios.get(
|
|
||||||
`/api/v1/indicators/currency/excel-down?file=${filename}&search_key=${data ? data : ''}
|
|
||||||
&start_dt=${sendDate}&end_dt=${endDate}`,
|
|
||||||
{
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
responseType: 'blob',
|
|
||||||
},
|
|
||||||
).then(response => {
|
|
||||||
const href = URL.createObjectURL(response.data);
|
|
||||||
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = href;
|
|
||||||
link.setAttribute('download', `${filename}`);
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
|
|
||||||
document.body.removeChild(link);
|
|
||||||
URL.revokeObjectURL(href);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
throw new Error('ClothesIndexExport Error', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// DAU
|
|
||||||
export const DailyActiveUserView = async (token, start_dt, end_dt) => {
|
|
||||||
try {
|
|
||||||
const res = await Axios.get(`/api/v1/indicators/dau/list?start_dt=${start_dt}&end_dt=${end_dt}`, {
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.data.data.dau_list;
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
throw new Error('DailyActiveUserView Error', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// DAU 다운로드
|
|
||||||
export const DailyActiveUserExport = async (token, filename, sendDate, endDate) => {
|
|
||||||
try {
|
|
||||||
await Axios.get(`/api/v1/indicators/dau/excel-down?file=${filename}&start_dt=${sendDate}&end_dt=${endDate}`, {
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
responseType: 'blob',
|
|
||||||
}).then(response => {
|
|
||||||
const href = URL.createObjectURL(response.data);
|
|
||||||
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = href;
|
|
||||||
link.setAttribute('download', `${filename}`);
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
|
|
||||||
document.body.removeChild(link);
|
|
||||||
URL.revokeObjectURL(href);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
throw new Error('PlaytimeIndexExport Error', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Daily Medal
|
|
||||||
export const DailyMedalView = async (token, start_dt, end_dt) => {
|
|
||||||
try {
|
|
||||||
const res = await Axios.get(`/api/v1/indicators/daily-medal/list?start_dt=${start_dt}&end_dt=${end_dt}`, {
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.data.data.daily_medal_list;
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
throw new Error('DailyMedalView Error', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
185
src/apis/Log.js
185
src/apis/Log.js
@@ -111,7 +111,6 @@ export const getCurrencyDetailList = async (token, searchType, searchData, tranI
|
|||||||
|
|
||||||
export const GameCurrencyDetailLogExport = async (token, params, fileName) => {
|
export const GameCurrencyDetailLogExport = async (token, params, fileName) => {
|
||||||
try {
|
try {
|
||||||
console.log(params);
|
|
||||||
await Axios.post(`/api/v1/log/currency/detail/excel-export`, params, {
|
await Axios.post(`/api/v1/log/currency/detail/excel-export`, params, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
@@ -127,4 +126,188 @@ export const GameCurrencyDetailLogExport = async (token, params, fileName) => {
|
|||||||
throw new Error('GameCurrencyDetailLogExport Error', e);
|
throw new Error('GameCurrencyDetailLogExport Error', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getItemDetailList = async (token, searchType, searchData, itemId, 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}&item_id=${itemId}
|
||||||
|
&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 {
|
||||||
|
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 {
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUserSnapshotList = async (token, searchType, searchData, startDate, endDate, order, size, currentPage) => {
|
||||||
|
try {
|
||||||
|
const response = await Axios.get(`/api/v1/log/user/snapshot/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('getUserSnapshotList API error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GameUserSnapshotLogExport = async (token, params, fileName) => {
|
||||||
|
try {
|
||||||
|
await Axios.post(`/api/v1/log/user/snapshot/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('GameUserSnapshotLogExport 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;
|
||||||
|
|||||||
99
src/apis/Rank.js
Normal file
99
src/apis/Rank.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
//운영서비스 관리 - 랭킹 스케줄 api 연결
|
||||||
|
|
||||||
|
import { Axios } from '../utils';
|
||||||
|
|
||||||
|
// 리스트 조회
|
||||||
|
export const RankingScheduleView = async (token, title, content, status, startDate, endDate, order, size, currentPage) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.get(
|
||||||
|
`/api/v1/rank/schedule/list?title=${title}&content=${content}&status=${status}&start_dt=${startDate}&end_dt=${endDate}
|
||||||
|
&orderby=${order}&page_no=${currentPage}&page_size=${size}`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.data.data;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('RankingScheduleView Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 전투시스템 상세보기
|
||||||
|
export const RankingScheduleDetailView = async (token, id) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.get(`/api/v1/rank/schedule/detail/${id}`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.data.data;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('RankingScheduleDetailView Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 랭킹스케줄 등록
|
||||||
|
export const RankingScheduleSingleRegist = async (token, params) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.post(`/api/v1/rank/schedule`, params, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.data;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('RankingScheduleSingleRegist Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 랭킹스케줄 수정
|
||||||
|
export const RankingScheduleModify = async (token, id, params) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.put(`/api/v1/rank/schedule/${id}`, params, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.data;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('RankingScheduleModify Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 랭킹스케줄 삭제
|
||||||
|
export const RankingScheduleDelete = async (token, id) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.delete(`/api/v1/rank/schedule/delete?id=${id}`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.data;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('RankingScheduleDelete Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RankingDataView = async (token) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.get(
|
||||||
|
`/api/v1/dictionary/ranking/list`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.data.data.ranking_list;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('RankingDataView Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
98
src/apis/RewardEvent.js
Normal file
98
src/apis/RewardEvent.js
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
//운영서비스 관리 - 이벤트 api 연결
|
||||||
|
|
||||||
|
import { Axios } from '../utils';
|
||||||
|
|
||||||
|
// 이벤트 리스트 조회
|
||||||
|
export const RewardEventView = async (token, title, content, status, startDate, endDate, order, size, currentPage) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.get(
|
||||||
|
`/api/v1/event/list?title=${title}&content=${content}&status=${status}&start_dt=${startDate}&end_dt=${endDate}
|
||||||
|
&orderby=${order}&page_no=${currentPage}&page_size=${size}`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.data.data;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('RewardEventView Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 이벤트 상세보기
|
||||||
|
export const RewardEventDetailView = async (token, id) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.get(`/api/v1/event/detail/${id}`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.data.data.detail;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('RewardEventDetailView Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 이벤트 등록
|
||||||
|
export const RewardEventSingleRegist = async (token, params) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.post(`/api/v1/event`, params, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('RewardEventSingleRegist Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 우편 수정
|
||||||
|
export const RewardEventModify = async (token, id, params) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.put(`/api/v1/event/${id}`, params, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.data;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('RewardEventModify Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 우편 삭제
|
||||||
|
export const RewardEventDelete = async (token, params, id) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.delete(`/api/v1/event/delete`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
data: { list: params },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.data.data.list;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('RewardEventDelete Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 이벤트 우편 아이템 확인
|
||||||
|
export const EventIsItem = async (token, params) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.post(`/api/v1/event/item`, params, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('EventIsItem Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -12,7 +12,7 @@ export const UserView = async (token, searchType, searchKey) => {
|
|||||||
{ headers: { Authorization: `Bearer ${token}` } },
|
{ headers: { Authorization: `Bearer ${token}` } },
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.data.data.result;
|
return res.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
throw new Error('UserView Error', e);
|
throw new Error('UserView Error', e);
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -12,12 +12,16 @@ export * from './BlackList';
|
|||||||
export * from './Users';
|
export * from './Users';
|
||||||
export * from './Indicators';
|
export * from './Indicators';
|
||||||
export * from './Item';
|
export * from './Item';
|
||||||
export * from './Event';
|
export * from './RewardEvent';
|
||||||
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';
|
||||||
|
export * from './Data';
|
||||||
|
export * from './Dictionary';
|
||||||
|
export * from './Rank';
|
||||||
|
export * from './Event';
|
||||||
|
|
||||||
const apiModules = {};
|
const apiModules = {};
|
||||||
const allApis = {};
|
const allApis = {};
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
18
src/assets/data/apis/eventAPI.json
Normal file
18
src/assets/data/apis/eventAPI.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"baseUrl": "/api/v1/world-event",
|
||||||
|
"endpoints": {
|
||||||
|
"EventView": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "/list",
|
||||||
|
"dataPath": "data",
|
||||||
|
"paramFormat": "query"
|
||||||
|
},
|
||||||
|
"EventDetailView": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "/detail/:id",
|
||||||
|
"dataPath": "data.data",
|
||||||
|
"paramFormat": "query",
|
||||||
|
"paramMapping": ["id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"LogViewList": {
|
"LogViewList": {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"url": "/list",
|
"url": "/list",
|
||||||
"dataPath": "data.data",
|
"dataPath": "data",
|
||||||
"paramFormat": "query"
|
"paramFormat": "query"
|
||||||
},
|
},
|
||||||
"LogviewDetail": {
|
"LogviewDetail": {
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import itemAPI from './itemAPI.json';
|
import itemAPI from './itemAPI.json';
|
||||||
import menuBannerAPI from './menuBannerAPI.json';
|
import menuBannerAPI from './menuBannerAPI.json';
|
||||||
import historyAPI from './historyAPI.json';
|
import historyAPI from './historyAPI.json';
|
||||||
|
import eventAPI from './eventAPI.json';
|
||||||
|
import rankingAPI from './rankingAPI.json';
|
||||||
|
import metaCraftingAPI from './metaCraftingAPI.json';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
itemAPI,
|
itemAPI,
|
||||||
menuBannerAPI,
|
menuBannerAPI,
|
||||||
historyAPI
|
historyAPI,
|
||||||
|
eventAPI,
|
||||||
|
rankingAPI,
|
||||||
|
metaCraftingAPI
|
||||||
};
|
};
|
||||||
11
src/assets/data/apis/metaCraftingAPI.json
Normal file
11
src/assets/data/apis/metaCraftingAPI.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"baseUrl": "/api/v1/dictionary/craft",
|
||||||
|
"endpoints": {
|
||||||
|
"getCraftingDictionaryList": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "/list",
|
||||||
|
"dataPath": "data",
|
||||||
|
"paramFormat": "query"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/assets/data/apis/rankingAPI.json
Normal file
18
src/assets/data/apis/rankingAPI.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"baseUrl": "/api/v1/rank/schedule",
|
||||||
|
"endpoints": {
|
||||||
|
"RankingScheduleView": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "/list",
|
||||||
|
"dataPath": "data",
|
||||||
|
"paramFormat": "query"
|
||||||
|
},
|
||||||
|
"RankingScheduleDetailView": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "/detail/:id",
|
||||||
|
"dataPath": "data.data",
|
||||||
|
"paramFormat": "query",
|
||||||
|
"paramMapping": ["id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
{
|
|
||||||
"baseUrl": "/api/v1/users",
|
|
||||||
"endpoints": {
|
|
||||||
"UserView": {
|
|
||||||
"method": "GET",
|
|
||||||
"url": "/api/v1/users/find-users",
|
|
||||||
"dataPath": "data.data.result",
|
|
||||||
"paramFormat": "query",
|
|
||||||
"paramMapping": ["search_type", "search_key"]
|
|
||||||
},
|
|
||||||
"UserInfoView": {
|
|
||||||
"method": "GET",
|
|
||||||
"url": "/api/v1/users/basicinfo",
|
|
||||||
"dataPath": "data.data",
|
|
||||||
"paramFormat": "query",
|
|
||||||
"paramMapping": ["guid"]
|
|
||||||
},
|
|
||||||
"UserChangeNickName": {
|
|
||||||
"method": "PUT",
|
|
||||||
"url": "/api/v1/users/change-nickname",
|
|
||||||
"dataPath": null,
|
|
||||||
"paramFormat": "body",
|
|
||||||
"paramMapping": ["guid", "nickname"]
|
|
||||||
},
|
|
||||||
"UserChangeAdminLevel": {
|
|
||||||
"method": "PUT",
|
|
||||||
"url": "/api/v1/users/change-level",
|
|
||||||
"dataPath": null,
|
|
||||||
"paramFormat": "body",
|
|
||||||
"paramMapping": ["guid", "level"]
|
|
||||||
},
|
|
||||||
"UserKick": {
|
|
||||||
"method": "PUT",
|
|
||||||
"url": "/api/v1/users/user-kick",
|
|
||||||
"dataPath": "data",
|
|
||||||
"paramFormat": "body",
|
|
||||||
"paramMapping": ["guid"]
|
|
||||||
},
|
|
||||||
"UserAvatarView": {
|
|
||||||
"method": "GET",
|
|
||||||
"url": "/api/v1/users/avatarinfo",
|
|
||||||
"dataPath": "data.data",
|
|
||||||
"paramFormat": "query",
|
|
||||||
"paramMapping": ["guid"]
|
|
||||||
},
|
|
||||||
"UserClothView": {
|
|
||||||
"method": "GET",
|
|
||||||
"url": "/api/v1/users/clothinfo",
|
|
||||||
"dataPath": "data.data",
|
|
||||||
"paramFormat": "query",
|
|
||||||
"paramMapping": ["guid"]
|
|
||||||
},
|
|
||||||
"UserToolView": {
|
|
||||||
"method": "GET",
|
|
||||||
"url": "/api/v1/users/toolslot",
|
|
||||||
"dataPath": "data.data",
|
|
||||||
"paramFormat": "query",
|
|
||||||
"paramMapping": ["guid"]
|
|
||||||
},
|
|
||||||
"UserInventoryView": {
|
|
||||||
"method": "GET",
|
|
||||||
"url": "/api/v1/users/inventory",
|
|
||||||
"dataPath": "data.data",
|
|
||||||
"paramFormat": "query",
|
|
||||||
"paramMapping": ["guid"]
|
|
||||||
},
|
|
||||||
"UserInventoryItemDelete": {
|
|
||||||
"method": "DELETE",
|
|
||||||
"url": "/api/v1/users/inventory/delete/item",
|
|
||||||
"dataPath": "data",
|
|
||||||
"paramFormat": "body",
|
|
||||||
"paramMapping": ["guid", "inventory_id"]
|
|
||||||
},
|
|
||||||
"UserTattooView": {
|
|
||||||
"method": "GET",
|
|
||||||
"url": "/api/v1/users/tattoo",
|
|
||||||
"dataPath": "data.data",
|
|
||||||
"paramFormat": "query",
|
|
||||||
"paramMapping": ["guid"]
|
|
||||||
},
|
|
||||||
"UserQuestView": {
|
|
||||||
"method": "GET",
|
|
||||||
"url": "/api/v1/users/quest",
|
|
||||||
"dataPath": "data.data",
|
|
||||||
"paramFormat": "query",
|
|
||||||
"paramMapping": ["guid"]
|
|
||||||
},
|
|
||||||
"UserFriendListView": {
|
|
||||||
"method": "GET",
|
|
||||||
"url": "/api/v1/users/friendlist",
|
|
||||||
"dataPath": "data.data",
|
|
||||||
"paramFormat": "query",
|
|
||||||
"paramMapping": ["guid"]
|
|
||||||
},
|
|
||||||
"UserMailView": {
|
|
||||||
"method": "POST",
|
|
||||||
"url": "/api/v1/users/mail",
|
|
||||||
"dataPath": "data.data",
|
|
||||||
"paramFormat": "body",
|
|
||||||
"paramMapping": ["guid", "page", "limit"]
|
|
||||||
},
|
|
||||||
"UserMailDelete": {
|
|
||||||
"method": "DELETE",
|
|
||||||
"url": "/api/v1/users/mail/delete",
|
|
||||||
"dataPath": "data",
|
|
||||||
"paramFormat": "body",
|
|
||||||
"paramMapping": ["mail_id"]
|
|
||||||
},
|
|
||||||
"UserMailItemDelete": {
|
|
||||||
"method": "DELETE",
|
|
||||||
"url": "/api/v1/users/mail/delete/item",
|
|
||||||
"dataPath": "data",
|
|
||||||
"paramFormat": "body",
|
|
||||||
"paramMapping": ["mail_id", "item_id"]
|
|
||||||
},
|
|
||||||
"UserMailDetailView": {
|
|
||||||
"method": "GET",
|
|
||||||
"url": "/api/v1/users/mail/:id",
|
|
||||||
"dataPath": "data.data",
|
|
||||||
"paramFormat": "path",
|
|
||||||
"paramMapping": ["id"]
|
|
||||||
},
|
|
||||||
"UserMyhomeView": {
|
|
||||||
"method": "GET",
|
|
||||||
"url": "/api/v1/users/myhome",
|
|
||||||
"dataPath": "data.data",
|
|
||||||
"paramFormat": "query",
|
|
||||||
"paramMapping": ["guid"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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: {
|
||||||
@@ -123,7 +123,7 @@ export const STATUS_STYLES = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const logFieldLabels = {
|
export const FieldLabels = {
|
||||||
// DynamoDB 필드
|
// DynamoDB 필드
|
||||||
'attribFieldName': '속성 명',
|
'attribFieldName': '속성 명',
|
||||||
'pk': '파티션 키',
|
'pk': '파티션 키',
|
||||||
@@ -181,15 +181,40 @@ export const logFieldLabels = {
|
|||||||
'ffa_hot_time': 'FFA 핫타임',
|
'ffa_hot_time': 'FFA 핫타임',
|
||||||
'round_count': '라운드 수',
|
'round_count': '라운드 수',
|
||||||
|
|
||||||
|
//dictionary
|
||||||
|
'max_count': '최대 보유 가능 수량',
|
||||||
|
'stack_max_count': '최대 스택 가능 수량',
|
||||||
|
'expire_type': '아이템 만료 타입',
|
||||||
|
'expire_start_dt': '만료 시작 시간',
|
||||||
|
'expire_end_dt': '만료 종료 시간',
|
||||||
|
'expire_time_sec': '만료 시간 연장 여부',
|
||||||
|
'user_tradable': '유저 간 거래 가능 여부',
|
||||||
|
'system_tradable': '상점에서 판매 가능 여부',
|
||||||
|
'throwable': '버리기 가능 여부',
|
||||||
|
'cart_buy': '상점에서 구매 가능 여부',
|
||||||
|
'rarity': '희귀도',
|
||||||
|
'default_attrib': '기본 속성',
|
||||||
|
'attrib_random_group': '랜덤 그룹',
|
||||||
|
'item_set': '아이템 세트',
|
||||||
|
'buff': '아이템 사용 시 획득 버프',
|
||||||
|
'dress_slot_type': '착용 부위',
|
||||||
|
'product_link': '제품 URL',
|
||||||
|
'prop_small_type': '제작 아이템 그룹',
|
||||||
|
'gacha_group_id': '랜덤박스 그룹 ID',
|
||||||
|
'ugq_action': 'UGQ 사용 가능 여부',
|
||||||
|
'linked_land': '연결된 랜드 ID',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const historyTables = {
|
export const historyTables = {
|
||||||
userBlock: 'black_list',
|
userBlock: 'black_list',
|
||||||
landAuction: 'land_auction',
|
landAuction: 'land_auction',
|
||||||
landOwnerChange: 'land_ownership_changes',
|
landOwnerChange: 'land_ownership_changes',
|
||||||
event: 'event',
|
rewardEvent: 'event',
|
||||||
mail: 'mail',
|
mail: 'mail',
|
||||||
notice: 'notice',
|
notice: 'notice',
|
||||||
battleEvent: 'battle_event',
|
battleEvent: 'battle_event',
|
||||||
caliumRequest: 'calium_request',
|
caliumRequest: 'calium_request',
|
||||||
|
menuBanner: 'menu_banner',
|
||||||
|
event: 'world_event',
|
||||||
|
rankingSchedule: 'ranking_schedule',
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export {authType, ivenTabType, modalTypes, TabUserList, tattooSlot, commonStatus, ViewTitleCountType, landAuctionStatusType} from './types'
|
export {authType, ivenTabType, modalTypes, tattooSlot, commonStatus, ViewTitleCountType, landAuctionStatusType} from './types'
|
||||||
export {
|
export {
|
||||||
mailSendType,
|
mailSendType,
|
||||||
mailType,
|
mailType,
|
||||||
@@ -27,6 +27,7 @@ export {
|
|||||||
opYNType,
|
opYNType,
|
||||||
opUserSessionType,
|
opUserSessionType,
|
||||||
opMailType,
|
opMailType,
|
||||||
amountDeltaType
|
amountDeltaType,
|
||||||
|
TabUserList
|
||||||
} from './options'
|
} from './options'
|
||||||
export {benItems, MinuteList, HourList, caliumRequestInitData, STATUS_STYLES, months, PAGE_SIZE_OPTIONS, ORDER_OPTIONS} from './data'
|
export {benItems, MinuteList, HourList, caliumRequestInitData, STATUS_STYLES, months, PAGE_SIZE_OPTIONS, ORDER_OPTIONS} from './data'
|
||||||
@@ -103,14 +103,6 @@ export const menuConfig = {
|
|||||||
view: false,
|
view: false,
|
||||||
authLevel: adminAuthLevel.NONE
|
authLevel: adminAuthLevel.NONE
|
||||||
},
|
},
|
||||||
cryptview: {
|
|
||||||
title: '크립토 조회',
|
|
||||||
permissions: {
|
|
||||||
read: authType.cryptoRead
|
|
||||||
},
|
|
||||||
view: false,
|
|
||||||
authLevel: adminAuthLevel.NONE
|
|
||||||
},
|
|
||||||
businesslogview: {
|
businesslogview: {
|
||||||
title: '비즈니스 로그 조회',
|
title: '비즈니스 로그 조회',
|
||||||
permissions: {
|
permissions: {
|
||||||
@@ -118,6 +110,30 @@ export const menuConfig = {
|
|||||||
},
|
},
|
||||||
view: true,
|
view: true,
|
||||||
authLevel: adminAuthLevel.NONE
|
authLevel: adminAuthLevel.NONE
|
||||||
|
},
|
||||||
|
itemdictionary: {
|
||||||
|
title: '아이템 백과사전 조회',
|
||||||
|
permissions: {
|
||||||
|
read: authType.itemDictionaryRead
|
||||||
|
},
|
||||||
|
view: true,
|
||||||
|
authLevel: adminAuthLevel.NONE
|
||||||
|
},
|
||||||
|
craftdictionary: {
|
||||||
|
title: '제작 아이템 조회',
|
||||||
|
permissions: {
|
||||||
|
read: authType.craftingDictionaryRead
|
||||||
|
},
|
||||||
|
view: true,
|
||||||
|
authLevel: adminAuthLevel.NONE
|
||||||
|
},
|
||||||
|
rankmanage: {
|
||||||
|
title: '랭킹 점수 관리',
|
||||||
|
permissions: {
|
||||||
|
read: authType.rankManagerRead
|
||||||
|
},
|
||||||
|
view: true,
|
||||||
|
authLevel: adminAuthLevel.NONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -154,17 +170,17 @@ export const menuConfig = {
|
|||||||
view: true,
|
view: true,
|
||||||
authLevel: adminAuthLevel.NONE
|
authLevel: adminAuthLevel.NONE
|
||||||
},
|
},
|
||||||
reportlist: {
|
// reportlist: {
|
||||||
title: '신고내역',
|
// title: '신고내역',
|
||||||
permissions: {
|
// permissions: {
|
||||||
read: authType.reportRead,
|
// read: authType.reportRead,
|
||||||
update: authType.reportUpdate,
|
// update: authType.reportUpdate,
|
||||||
delete: authType.reportDelete
|
// delete: authType.reportDelete
|
||||||
},
|
// },
|
||||||
view: true,
|
// view: true,
|
||||||
authLevel: adminAuthLevel.NONE
|
// authLevel: adminAuthLevel.NONE
|
||||||
},
|
// },
|
||||||
event: {
|
rewardevent: {
|
||||||
title: '보상 이벤트 관리',
|
title: '보상 이벤트 관리',
|
||||||
permissions: {
|
permissions: {
|
||||||
read: authType.eventRead,
|
read: authType.eventRead,
|
||||||
@@ -214,6 +230,26 @@ export const menuConfig = {
|
|||||||
view: true,
|
view: true,
|
||||||
authLevel: adminAuthLevel.NONE
|
authLevel: adminAuthLevel.NONE
|
||||||
},
|
},
|
||||||
|
ranking: {
|
||||||
|
title: '랭킹 스케줄러',
|
||||||
|
permissions: {
|
||||||
|
read: authType.rankingRead,
|
||||||
|
update: authType.rankingUpdate,
|
||||||
|
delete: authType.rankingDelete
|
||||||
|
},
|
||||||
|
view: true,
|
||||||
|
authLevel: adminAuthLevel.NONE
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
title: '통합 이벤트 관리',
|
||||||
|
permissions: {
|
||||||
|
read: authType.worldEventRead,
|
||||||
|
update: authType.worldEventUpdate,
|
||||||
|
delete: authType.worldEventDelete
|
||||||
|
},
|
||||||
|
view: true,
|
||||||
|
authLevel: adminAuthLevel.NONE
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -4,18 +4,50 @@ export const languageType = [
|
|||||||
{ value: 'JA', name: '일본어' },
|
{ value: 'JA', name: '일본어' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const TabUserList = [
|
||||||
|
{ value: 'BASIC', name: '기본정보' },
|
||||||
|
{ value: 'AVATAR', name: '아바타' },
|
||||||
|
{ value: 'CLOTH', name: '의상' },
|
||||||
|
{ value: 'TOOL', name: '도구' },
|
||||||
|
{ value: 'INVENTORY', name: '인벤토리' },
|
||||||
|
{ value: 'MAIL', name: '우편' },
|
||||||
|
{ value: 'MYHOME', name: '마이홈' },
|
||||||
|
{ value: 'FRIEND', name: '친구목록' },
|
||||||
|
{ value: 'TATTOO', name: '타투' },
|
||||||
|
{ value: 'QUEST', name: '퀘스트' }
|
||||||
|
// { value: 'CLAIM', name: '클레임' }
|
||||||
|
];
|
||||||
|
|
||||||
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: '유저로그인 로그' },
|
||||||
|
{ value: 'SNAPSHOT', name: '스냅샷 로그' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const TabEconomicIndexList = [
|
export const TabEconomicIndexList = [
|
||||||
{ value: 'CURRENCY', name: '재화(유저)' },
|
{ value: 'CURRENCY_ACQUIRE', name: '재화 획득' },
|
||||||
// { value: 'ITEM', name: '아이템' },
|
{ value: 'CURRENCY_CONSUME', name: '재화 소모' },
|
||||||
// { value: 'VBP', name: 'VBP' },
|
{ value: 'ITEM_ACQUIRE', name: '아이템 획득' },
|
||||||
// { value: 'deco', name: '의상/타투' },
|
{ value: 'ITEM_CONSUME', name: '아이템 소모' },
|
||||||
// { value: 'instance', name: '인스턴스' },
|
{ value: 'CURRENCY_ASSETS', name: '재화 보유' },
|
||||||
|
{ value: 'ITEM_ASSETS', name: '아이템 보유' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const TabUserIndexList = [
|
||||||
|
{ value: 'USER', name: '이용자 지표' },
|
||||||
|
{ value: 'RETENTION', name: '잔존율' },
|
||||||
|
{ value: 'CURRENCY', name: '재화' },
|
||||||
|
// { value: 'SEGMENT', name: 'Segment' },
|
||||||
|
// { value: 'PLAYTIME', name: '플레이타임' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const TabRankManageList = [
|
||||||
|
{ value: 'PIONEER', name: '개척자 랭킹 보드' },
|
||||||
|
{ value: 'RUN_RACE', name: '점프 러너 랭킹 보드' },
|
||||||
|
{ value: 'BATTLE_OBJECT', name: '컴뱃 존 랭킹 보드' }
|
||||||
];
|
];
|
||||||
|
|
||||||
export const mailSendType = [
|
export const mailSendType = [
|
||||||
@@ -95,6 +127,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: '사파이어' },
|
||||||
@@ -197,6 +240,11 @@ export const landSearchType = [
|
|||||||
{ value: 'NAME', name: '랜드명' },
|
{ value: 'NAME', name: '랜드명' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const itemSearchType = [
|
||||||
|
{ value: 'ID', name: '아이템ID' },
|
||||||
|
{ value: 'NAME', name: '아이템명' },
|
||||||
|
];
|
||||||
|
|
||||||
export const blockType = [
|
export const blockType = [
|
||||||
{ value: '', name: '선택' },
|
{ value: '', name: '선택' },
|
||||||
{ value: 'Access_Restrictions', name: '접근 제한' },
|
{ value: 'Access_Restrictions', name: '접근 제한' },
|
||||||
@@ -225,6 +273,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: '대기' },
|
||||||
@@ -340,6 +406,19 @@ export const opItemRestore = [
|
|||||||
{ value: 'IMPOSSIBLE', name: '불가능' },
|
{ value: 'IMPOSSIBLE', name: '불가능' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const opPropSmallType = [
|
||||||
|
{ value: 'ALL', name: '전체' },
|
||||||
|
{ value: 'FURNITURE', name: 'FURNITURE' },
|
||||||
|
{ value: 'COOKING', name: 'COOKING' },
|
||||||
|
{ value: 'CLOTHES', name: 'CLOTHES' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const opPropRecipeType = [
|
||||||
|
{ value: 'ALL', name: '전체' },
|
||||||
|
{ value: 'Basic', name: '기본' },
|
||||||
|
{ value: 'Add', name: '등록 필요' },
|
||||||
|
];
|
||||||
|
|
||||||
export const opEquipType = [
|
export const opEquipType = [
|
||||||
{ value: 0, name: '미장착' },
|
{ value: 0, name: '미장착' },
|
||||||
{ value: 1, name: '의상장착' },
|
{ value: 1, name: '의상장착' },
|
||||||
@@ -347,7 +426,7 @@ export const opEquipType = [
|
|||||||
{ value: 3, name: '타투장착' },
|
{ value: 3, name: '타투장착' },
|
||||||
]
|
]
|
||||||
|
|
||||||
export const opItemType = [
|
export const opItemLargeType = [
|
||||||
{ value: 'TOOL', name: '도구' },
|
{ value: 'TOOL', name: '도구' },
|
||||||
{ value: 'EXPENDABLE', name: '소모품' },
|
{ value: 'EXPENDABLE', name: '소모품' },
|
||||||
{ value: 'TICKET', name: '티켓' },
|
{ value: 'TICKET', name: '티켓' },
|
||||||
@@ -359,6 +438,95 @@ export const opItemType = [
|
|||||||
{ value: 'CURRENCY', name: '재화' },
|
{ value: 'CURRENCY', name: '재화' },
|
||||||
{ value: 'PRODUCT', name: '제품' },
|
{ value: 'PRODUCT', name: '제품' },
|
||||||
{ value: 'BEAUTY', name: '뷰티' },
|
{ value: 'BEAUTY', name: '뷰티' },
|
||||||
|
{ value: 'SET_BOX', name: '세트 박스' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const opItemSmallType = [
|
||||||
|
{ value: 'HANDMIRROR', name: '손거울' },
|
||||||
|
{ value: 'LIGHTSTICK', name: '응원봉' },
|
||||||
|
{ value: 'FIRECRACKER', name: '폭죽총' },
|
||||||
|
{ value: 'LIGHTSABER', name: '광선검' },
|
||||||
|
{ value: 'REGISTER_ITEM_SOCIAL_ACTION', name: '소셜액션 등록형 아이템' },
|
||||||
|
{ value: 'REGISTER_ITEM_INTERIOR', name: '인테리어 등록형 아이템' },
|
||||||
|
{ value: 'SAIYAN_AURA', name: '초사이어인 오라' },
|
||||||
|
{ value: 'TICKET', name: '소모형 티켓' },
|
||||||
|
{ value: 'RANDOMBOX', name: '골드(재화) 가챠 랜덤 박스' },
|
||||||
|
{ value: 'SHIRT', name: '상의' },
|
||||||
|
{ value: 'DRESS', name: '드레스' },
|
||||||
|
{ value: 'OUTER', name: '겉옷' },
|
||||||
|
{ value: 'PANTS', name: '하의' },
|
||||||
|
{ value: 'GLOVES', name: '장갑' },
|
||||||
|
{ value: 'RING', name: '반지' },
|
||||||
|
{ value: 'BRACELET', name: '팔찌' },
|
||||||
|
{ value: 'BAG', name: '가방(크로스백)' },
|
||||||
|
{ value: 'BACKPACK', name: '가방(백팩)' },
|
||||||
|
{ value: 'CAP', name: '모자' },
|
||||||
|
{ value: 'MASK', name: '가면' },
|
||||||
|
{ value: 'GLASSES', name: '안경' },
|
||||||
|
{ value: 'EARRING', name: '귀걸이' },
|
||||||
|
{ value: 'NECKLACE', name: '목걸이' },
|
||||||
|
{ value: 'SHOES', name: '신발' },
|
||||||
|
{ value: 'SOCKS', name: '양말' },
|
||||||
|
{ value: 'ANKLET', name: '발찌' },
|
||||||
|
{ value: 'OFFICECHAIR', name: '의자' },
|
||||||
|
{ value: 'WALLMOUNTTV', name: '벽걸이TV' },
|
||||||
|
{ value: 'OUTDOORCHAIR', name: '야외용의자' },
|
||||||
|
{ value: 'TV', name: 'TV' },
|
||||||
|
{ value: 'VIGNETTE', name: '소품' },
|
||||||
|
{ value: 'COOKWARE', name: '조리도구' },
|
||||||
|
{ value: 'KITCHEN_TOOL', name: '부엌도구' },
|
||||||
|
{ value: 'LAPTOP', name: '노트북' },
|
||||||
|
{ value: 'OUTDOOR_GOODS', name: '캠핑도구' },
|
||||||
|
{ value: 'BED', name: '침대' },
|
||||||
|
{ value: 'DECO', name: '소형장식품' },
|
||||||
|
{ value: 'FURNITURE', name: '(러그)가구' },
|
||||||
|
{ value: 'MUSIC', name: '악기/음향' },
|
||||||
|
{ value: 'SHELF_S', name: '소형 선반(TV다이)' },
|
||||||
|
{ value: 'SHELF_L', name: '대형 선반(금고,책장)' },
|
||||||
|
{ value: 'SOFA_SINGLE', name: '1인용 소파' },
|
||||||
|
{ value: 'SOFA_COUCH', name: '카우치 소파' },
|
||||||
|
{ value: 'LIGHT_CEILING', name: '천정 조명' },
|
||||||
|
{ value: 'LIGHT_FLOOR', name: '스탠드 조명' },
|
||||||
|
{ value: 'LIGHT_TABLE', name: '탁상 조명' },
|
||||||
|
{ value: 'LIGHT_PENDENT', name: '팬던트 조명' },
|
||||||
|
{ value: 'TABLE_S', name: '소형 테이블' },
|
||||||
|
{ value: 'TABLE_L', name: '대형 테이블' },
|
||||||
|
{ value: 'TABLE_LIVINGROOM', name: '거실 테이블' },
|
||||||
|
{ value: 'TABLE_OFFICE', name: '사무용 테이블' },
|
||||||
|
{ value: 'LEISURE_APPLIANCE', name: '스포츠/여가' },
|
||||||
|
{ value: 'INDUCTION', name: '인덕션' },
|
||||||
|
{ value: 'MICROWAVE', name: '전자레인지' },
|
||||||
|
{ value: 'LARGE_APPLIANCE', name: '대형가전' },
|
||||||
|
{ value: 'COSMETIC', name: '화장품' },
|
||||||
|
{ value: 'CHEST', name: '앞면' },
|
||||||
|
{ value: 'LEFT_ARM', name: '왼팔' },
|
||||||
|
{ value: 'RIGHT_ARM', name: '오른팔' },
|
||||||
|
{ value: 'BACK', name: '후면' },
|
||||||
|
{ value: 'LEFT_LEG', name: '왼다리' },
|
||||||
|
{ value: 'RIGHT_LEG', name: '오른다리' },
|
||||||
|
{ value: 'CARTRIDGE', name: '속성 카트리지' },
|
||||||
|
{ value: 'BUFF_DRINK', name: '드링크(물약) 아이템' },
|
||||||
|
{ value: 'INTERPHONE', name: '인터폰' },
|
||||||
|
{ value: 'MEGAPHONE', name: '확성기' },
|
||||||
|
{ value: 'CURRENCY', name: 'CURRENCY' },
|
||||||
|
{ value: 'NFTLAND', name: 'NFTLAND' },
|
||||||
|
{ value: 'SUMMONSTONE', name: '소환석' },
|
||||||
|
{ value: 'GOLD', name: '골드' },
|
||||||
|
{ value: 'SAPPHIRE', name: '사파이어' },
|
||||||
|
{ value: 'CALIUM', name: '칼리움' },
|
||||||
|
{ value: 'BEAM', name: '빔' },
|
||||||
|
{ value: 'RUBY', name: '루비' },
|
||||||
|
{ value: 'LIGHT_LIMITED', name: '언리얼 라이트 사용 조명' },
|
||||||
|
{ value: 'SPEAKER', name: '재생 기능성 스피커' },
|
||||||
|
{ value: 'SETBOX', name: '세트박스' },
|
||||||
|
{ value: 'DRESS_SHOES', name: '드레스+신발' },
|
||||||
|
{ value: 'SHOULDERBAG', name: '숄더백' },
|
||||||
|
{ value: 'RECIPE', name: '레시피' }
|
||||||
|
]
|
||||||
|
|
||||||
|
export const opGender = [
|
||||||
|
{ value: 'MALE', name: '남성' },
|
||||||
|
{ value: 'FEMALE', name: '여성' },
|
||||||
]
|
]
|
||||||
|
|
||||||
export const opHistoryType = [
|
export const opHistoryType = [
|
||||||
@@ -430,6 +598,62 @@ export const opDBType = [
|
|||||||
{ value: 'MySql', name: 'MySql'},
|
{ value: 'MySql', name: 'MySql'},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const opLogCategory = [
|
||||||
|
{ value: 'SCHEDULER', name: '스케줄러'},
|
||||||
|
{ value: 'DYNAMODB', name: 'DynamoDB'},
|
||||||
|
{ value: 'MARIADB', name: 'MariaDB'},
|
||||||
|
{ value: 'MESSAGE_QUEUE', name: '메시지큐'},
|
||||||
|
{ value: 'REDIS', name: 'Redis'},
|
||||||
|
{ value: 'S3', name: 'S3'},
|
||||||
|
{ value: 'BATCH_JOB', name: '배치잡'},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const opLogAction = [
|
||||||
|
{ value: 'KICK_USER', name: '유저킥' },
|
||||||
|
{ value: 'ADMIN_LEVEL', name: 'GM 레벨' },
|
||||||
|
{ value: 'NICKNAME_CHANGE', name: '아바타명 변경' },
|
||||||
|
{ value: 'MAIL_ITEM', name: '메일 아이템' },
|
||||||
|
{ value: 'QUEST_TASK', name: '퀘스트 Task' },
|
||||||
|
{ value: 'SCHEDULE_CLEANUP', name: '스케줄 캐시정리' },
|
||||||
|
{ value: 'SCHEDULE_DATA_INIT', name: '스케줄 데이터 초기화' },
|
||||||
|
{ value: 'SCHEDULE_LAND_OWNER_CHANGE', name: '스케줄 랜드 소유자 변경' },
|
||||||
|
{ value: 'SCHEDULE_BLACK_LIST', name: '스케줄 이용자 제재' },
|
||||||
|
{ value: 'SCHEDULE_NOTICE', name: '스케줄 인게임메시지' },
|
||||||
|
{ value: 'SCHEDULE_MAIL', name: '스케줄 우편' },
|
||||||
|
{ value: 'SCHEDULE_EVENT', name: '스케줄 이벤트' },
|
||||||
|
{ value: 'SCHEDULE_BATTLE_EVENT', name: '스케줄 전투 이벤트' },
|
||||||
|
{ value: 'SCHEDULE_LAND_AUCTION', name: '스케줄 랜드 경매' },
|
||||||
|
{ value: 'BANNER', name: '메뉴 배너' },
|
||||||
|
{ value: 'BATTLE_EVENT', name: '전투 이벤트' },
|
||||||
|
{ value: 'BUILDING', name: '빌딩' },
|
||||||
|
{ value: 'LAND_OWNER_CHANGE', name: '랜드 소유자 변경' },
|
||||||
|
{ value: 'LAND_AUCTION', name: '랜드 경매' },
|
||||||
|
{ value: 'GROUP', name: '그룹' },
|
||||||
|
{ value: 'ADMIN', name: '운영자' },
|
||||||
|
{ value: 'ADMIN_GROUP', name: '운영자 그룹' },
|
||||||
|
{ value: 'ADMIN_DELETE', name: '운영자 삭제' },
|
||||||
|
{ value: 'AUTH_ADMIN', name: '운영자 권한' },
|
||||||
|
{ value: 'PASSWORD_INIT', name: '비밀번호 초기화' },
|
||||||
|
{ value: 'PASSWORD_CHANGE', name: '비밀번호 변경' },
|
||||||
|
{ value: 'BLACK_LIST', name: '이용자 제재' },
|
||||||
|
{ value: 'CALIUM_REQUEST', name: '칼리움 요청' },
|
||||||
|
{ value: 'EVENT', name: '이벤트' },
|
||||||
|
{ value: 'MAIL', name: '우편' },
|
||||||
|
{ value: 'NOTICE', name: '인게임메시지' },
|
||||||
|
{ value: 'DATA_INIT', name: '데이터 초기화' },
|
||||||
|
{ value: 'DATA', name: '데이터' },
|
||||||
|
{ value: 'USER', name: '사용자' },
|
||||||
|
{ value: 'ITEM', name: '아이템' }
|
||||||
|
]
|
||||||
|
|
||||||
|
export const opCommonStatus = [
|
||||||
|
{ value: 'SUCCESS', name: '성공' },
|
||||||
|
{ value: 'FAIL', name: '실패' },
|
||||||
|
{ value: 'WAIT', name: '대기' },
|
||||||
|
{ value: 'END', name: '종료' },
|
||||||
|
{ value: 'RUNNING', name: '진행중' },
|
||||||
|
]
|
||||||
|
|
||||||
// export const logAction = [
|
// export const logAction = [
|
||||||
// { value: "None", name: "ALL" },
|
// { value: "None", name: "ALL" },
|
||||||
// { value: "AIChatDeleteCharacter", name: "NPC 삭제" },
|
// { value: "AIChatDeleteCharacter", name: "NPC 삭제" },
|
||||||
@@ -726,6 +950,7 @@ export const opDBType = [
|
|||||||
|
|
||||||
export const logAction = [
|
export const logAction = [
|
||||||
{ value: "None", name: "전체" },
|
{ value: "None", name: "전체" },
|
||||||
|
{ value: "AdminToolQuestTaskForceComplete", name: "AdminToolQuestTaskForceComplete" },
|
||||||
{ value: "AIChatDeleteCharacter", name: "AIChatDeleteCharacter" },
|
{ value: "AIChatDeleteCharacter", name: "AIChatDeleteCharacter" },
|
||||||
{ value: "AIChatDeleteUser", name: "AIChatDeleteUser" },
|
{ value: "AIChatDeleteUser", name: "AIChatDeleteUser" },
|
||||||
{ value: "AIChatGetCharacter", name: "AIChatGetCharacter" },
|
{ value: "AIChatGetCharacter", name: "AIChatGetCharacter" },
|
||||||
@@ -760,12 +985,12 @@ export const logAction = [
|
|||||||
{ value: "BeaconShopUpdateDailyCount", name: "BeaconShopUpdateDailyCount" },
|
{ value: "BeaconShopUpdateDailyCount", name: "BeaconShopUpdateDailyCount" },
|
||||||
{ value: "BeaconShopDeleteRecord", name: "BeaconShopDeleteRecord" },
|
{ value: "BeaconShopDeleteRecord", name: "BeaconShopDeleteRecord" },
|
||||||
{ value: "BeaconShopDeactiveItems", name: "BeaconShopDeactiveItems" },
|
{ value: "BeaconShopDeactiveItems", name: "BeaconShopDeactiveItems" },
|
||||||
{ value: "BrokerApiAdmin", name: "BrokerApiAdmin" },
|
// { value: "BrokerApiAdmin", name: "BrokerApiAdmin" },
|
||||||
{ value: "BrokerApiPlanetAuth", name: "BrokerApiPlanetAuth" },
|
{ value: "BrokerApiPlanetAuth", name: "BrokerApiPlanetAuth" },
|
||||||
{ value: "BrokerApiUserExchangeOrderCompleted", name: "BrokerApiUserExchangeOrderCompleted" },
|
{ value: "BrokerApiUserExchangeOrderCompleted", name: "BrokerApiUserExchangeOrderCompleted" },
|
||||||
{ value: "BrokerApiUserExchangeOrderCreated", name: "BrokerApiUserExchangeOrderCreated" },
|
{ value: "BrokerApiUserExchangeOrderCreated", name: "BrokerApiUserExchangeOrderCreated" },
|
||||||
{ value: "BrokerApiUserSystemMailSend", name: "BrokerApiUserSystemMailSend" },
|
// { value: "BrokerApiUserSystemMailSend", name: "BrokerApiUserSystemMailSend" },
|
||||||
{ value: "BrokerApiUserEchoSystemRequest", name: "BrokerApiUserEchoSystemRequest" },
|
// { value: "BrokerApiUserEchoSystemRequest", name: "BrokerApiUserEchoSystemRequest" },
|
||||||
{ value: "BrokerApiUserLogin", name: "BrokerApiUserLogin" },
|
{ value: "BrokerApiUserLogin", name: "BrokerApiUserLogin" },
|
||||||
{ value: "BuffAdd", name: "BuffAdd" },
|
{ value: "BuffAdd", name: "BuffAdd" },
|
||||||
{ value: "BuffDelete", name: "BuffDelete" },
|
{ value: "BuffDelete", name: "BuffDelete" },
|
||||||
@@ -815,6 +1040,7 @@ export const logAction = [
|
|||||||
{ value: "ClaimReward", name: "ClaimReward" },
|
{ value: "ClaimReward", name: "ClaimReward" },
|
||||||
{ value: "ConvertCalium", name: "ConvertCalium" },
|
{ value: "ConvertCalium", name: "ConvertCalium" },
|
||||||
{ value: "ConvertExchangeCalium", name: "ConvertExchangeCalium" },
|
{ value: "ConvertExchangeCalium", name: "ConvertExchangeCalium" },
|
||||||
|
{ value: "ContentsMove", name: "ContentsMove" },
|
||||||
{ value: "CraftFinish", name: "CraftFinish" },
|
{ value: "CraftFinish", name: "CraftFinish" },
|
||||||
{ value: "CraftHelp", name: "CraftHelp" },
|
{ value: "CraftHelp", name: "CraftHelp" },
|
||||||
{ value: "CraftRecipeRegister", name: "CraftRecipeRegister" },
|
{ value: "CraftRecipeRegister", name: "CraftRecipeRegister" },
|
||||||
@@ -841,6 +1067,12 @@ export const logAction = [
|
|||||||
{ value: "FriendAdd", name: "FriendAdd" },
|
{ value: "FriendAdd", name: "FriendAdd" },
|
||||||
{ value: "FriendDelete", name: "FriendDelete" },
|
{ value: "FriendDelete", name: "FriendDelete" },
|
||||||
{ value: "GainLandProfit", name: "GainLandProfit" },
|
{ value: "GainLandProfit", name: "GainLandProfit" },
|
||||||
|
{ value: "GameModeObjectInteraction", name: "GameModeObjectInteraction" },
|
||||||
|
{ value: "GameModeObjectStateUpdate", name: "GameModeObjectStateUpdate" },
|
||||||
|
{ value: "GameModeUserDead", name: "GameModeUserDead" },
|
||||||
|
{ value: "GameModeUserRespawn", name: "GameModeUserRespawn" },
|
||||||
|
{ value: "GameModePenalty", name: "GameModePenalty" },
|
||||||
|
{ value: "GameModeAddMatchCount", name: "GameModeAddMatchCount" },
|
||||||
{ value: "InviteParty", name: "InviteParty" },
|
{ value: "InviteParty", name: "InviteParty" },
|
||||||
{ value: "ItemBuy", name: "ItemBuy" },
|
{ value: "ItemBuy", name: "ItemBuy" },
|
||||||
{ value: "ItemDestroy", name: "ItemDestroy" },
|
{ value: "ItemDestroy", name: "ItemDestroy" },
|
||||||
@@ -871,8 +1103,17 @@ export const logAction = [
|
|||||||
{ value: "MailRead", name: "MailRead" },
|
{ value: "MailRead", name: "MailRead" },
|
||||||
{ value: "MailSend", name: "MailSend" },
|
{ value: "MailSend", name: "MailSend" },
|
||||||
{ value: "MailTaken", name: "MailTaken" },
|
{ value: "MailTaken", name: "MailTaken" },
|
||||||
|
{ value: "MatchReserve", name: "MatchReserve" },
|
||||||
|
{ value: "MatchCancel", name: "MatchCancel" },
|
||||||
|
{ value: "MatchResult", name: "MatchResult" },
|
||||||
|
{ value: "MatchRoomUserJoin", name: "MatchRoomUserJoin" },
|
||||||
|
{ value: "MatchRoomUserQuit", name: "MatchRoomUserQuit" },
|
||||||
|
{ value: "MatchRoomCreate", name: "MatchRoomCreate" },
|
||||||
|
{ value: "MatchRoomUpdate", name: "MatchRoomUpdate" },
|
||||||
|
{ value: "MatchRoomDestroy", name: "MatchRoomDestroy" },
|
||||||
{ value: "ModifyLandInfo", name: "ModifyLandInfo" },
|
{ value: "ModifyLandInfo", name: "ModifyLandInfo" },
|
||||||
{ value: "MoneyChange", name: "MoneyChange" },
|
{ value: "MoneyChange", name: "MoneyChange" },
|
||||||
|
{ value: "MoveToBeacon", name: "MoveToBeacon" },
|
||||||
{ value: "ProductGive", name: "ProductGive" },
|
{ value: "ProductGive", name: "ProductGive" },
|
||||||
{ value: "ProductOpenFailed", name: "ProductOpenFailed" },
|
{ value: "ProductOpenFailed", name: "ProductOpenFailed" },
|
||||||
{ value: "ProductOpenSuccess", name: "ProductOpenSuccess" },
|
{ value: "ProductOpenSuccess", name: "ProductOpenSuccess" },
|
||||||
@@ -896,6 +1137,11 @@ export const logAction = [
|
|||||||
{ value: "ReplySummonParty", name: "ReplySummonParty" },
|
{ value: "ReplySummonParty", name: "ReplySummonParty" },
|
||||||
{ value: "ReservationEnterToServer", name: "ReservationEnterToServer" },
|
{ value: "ReservationEnterToServer", name: "ReservationEnterToServer" },
|
||||||
{ value: "RewardProp", name: "RewardProp" },
|
{ value: "RewardProp", name: "RewardProp" },
|
||||||
|
{ value: "RunRaceFinishReward", name: "RunRaceFinishReward" },
|
||||||
|
{ value: "RunRaceRespawnReward", name: "RunRaceRespawnReward" },
|
||||||
|
{ value: "RunRaceUnFinishReward", name: "RunRaceUnFinishReward" },
|
||||||
|
{ value: "RunRaceCheckPointAbusing", name: "RunRaceCheckPointAbusing" },
|
||||||
|
{ value: "RunRaceResultSummary", name: "RunRaceResultSummary" },
|
||||||
{ value: "SaveMyhome", name: "SaveMyhome" },
|
{ value: "SaveMyhome", name: "SaveMyhome" },
|
||||||
{ value: "SeasonPassBuyCharged", name: "SeasonPassBuyCharged" },
|
{ value: "SeasonPassBuyCharged", name: "SeasonPassBuyCharged" },
|
||||||
{ value: "SeasonPassStartNew", name: "SeasonPassStartNew" },
|
{ value: "SeasonPassStartNew", name: "SeasonPassStartNew" },
|
||||||
@@ -945,6 +1191,7 @@ export const logAction = [
|
|||||||
{ value: "UpdateGameOption", name: "UpdateGameOption" },
|
{ value: "UpdateGameOption", name: "UpdateGameOption" },
|
||||||
{ value: "UpdateLanguage", name: "UpdateLanguage" },
|
{ value: "UpdateLanguage", name: "UpdateLanguage" },
|
||||||
{ value: "UpdateUgcNpcLike", name: "UpdateUgcNpcLike" },
|
{ value: "UpdateUgcNpcLike", name: "UpdateUgcNpcLike" },
|
||||||
|
{ value: "UpdateGameModePlayerRegulation", name: "UpdateGameModePlayerRegulation" },
|
||||||
{ value: "UserBlock", name: "UserBlock" },
|
{ value: "UserBlock", name: "UserBlock" },
|
||||||
{ value: "UserBlockCancel", name: "UserBlockCancel" },
|
{ value: "UserBlockCancel", name: "UserBlockCancel" },
|
||||||
{ value: "UserCreate", name: "UserCreate" },
|
{ value: "UserCreate", name: "UserCreate" },
|
||||||
@@ -997,6 +1244,9 @@ export const logDomain = [
|
|||||||
{ value: "Farming", name: "Farming" },
|
{ value: "Farming", name: "Farming" },
|
||||||
{ value: "FarmingReward", name: "FarmingReward" },
|
{ value: "FarmingReward", name: "FarmingReward" },
|
||||||
{ value: "GameLogInOut", name: "GameLogInOut" },
|
{ value: "GameLogInOut", name: "GameLogInOut" },
|
||||||
|
{ value: "GameObjectInteraction", name: "GameObjectInteraction" },
|
||||||
|
{ value: "GameModePenalty", name: "GameModePenalty" },
|
||||||
|
{ value: "GameModePlayRegulation", name: "GameModePlayRegulation" },
|
||||||
{ value: "Item", name: "Item" },
|
{ value: "Item", name: "Item" },
|
||||||
{ value: "IgmApi", name: "IgmApi" },
|
{ value: "IgmApi", name: "IgmApi" },
|
||||||
{ value: "MyHome", name: "MyHome" },
|
{ value: "MyHome", name: "MyHome" },
|
||||||
@@ -1008,6 +1258,9 @@ export const logDomain = [
|
|||||||
{ value: "Mail", name: "Mail" },
|
{ value: "Mail", name: "Mail" },
|
||||||
{ value: "MailStoragePeriodExpired", name: "MailStoragePeriodExpired" },
|
{ value: "MailStoragePeriodExpired", name: "MailStoragePeriodExpired" },
|
||||||
{ value: "MailProfile", name: "MailProfile" },
|
{ value: "MailProfile", name: "MailProfile" },
|
||||||
|
{ value: "MatchUser", name: "MatchUser" },
|
||||||
|
{ value: "MatchServerUser", name: "MatchServerUser" },
|
||||||
|
{ value: "MatchRoom", name: "MatchRoom" },
|
||||||
{ value: "Party", name: "Party" },
|
{ value: "Party", name: "Party" },
|
||||||
{ value: "PartyMember", name: "PartyMember" },
|
{ value: "PartyMember", name: "PartyMember" },
|
||||||
{ value: "PartyVote", name: "PartyVote" },
|
{ value: "PartyVote", name: "PartyVote" },
|
||||||
@@ -1025,6 +1278,11 @@ export const logDomain = [
|
|||||||
{ value: "RenewalShopProducts", name: "RenewalShopProducts" },
|
{ value: "RenewalShopProducts", name: "RenewalShopProducts" },
|
||||||
{ value: "Rental", name: "Rental" },
|
{ value: "Rental", name: "Rental" },
|
||||||
{ value: "RewardProp", name: "RewardProp" },
|
{ value: "RewardProp", name: "RewardProp" },
|
||||||
|
{ value: "RunRaceFinishReward", name: "RunRaceFinishReward" },
|
||||||
|
{ value: "RunRaceRespawnReward", name: "RunRaceRespawnReward" },
|
||||||
|
{ value: "RunRaceUnFinishReward", name: "RunRaceUnFinishReward" },
|
||||||
|
{ value: "RunRaceCheckPointAbusing", name: "RunRaceCheckPointAbusing" },
|
||||||
|
{ value: "RunRaceRewardSummary", name: "RunRaceRewardSummary" },
|
||||||
{ value: "Stage", name: "Stage" },
|
{ value: "Stage", name: "Stage" },
|
||||||
{ value: "SocialAction", name: "SocialAction" },
|
{ value: "SocialAction", name: "SocialAction" },
|
||||||
{ value: "SeasonPass", name: "SeasonPass" },
|
{ value: "SeasonPass", name: "SeasonPass" },
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"initialSearchParams": {
|
"initialSearchParams": {
|
||||||
"searchTitle": "",
|
"searchData": "",
|
||||||
"searchContent": "",
|
|
||||||
"status": "ALL",
|
"status": "ALL",
|
||||||
"startDate": "",
|
"startDate": "",
|
||||||
"endDate": "",
|
"endDate": "",
|
||||||
@@ -13,51 +12,37 @@
|
|||||||
"searchFields": [
|
"searchFields": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"id": "searchTitle",
|
"id": "searchData",
|
||||||
"label": "우편 제목",
|
"label": "제목",
|
||||||
"placeholder": "제목 입력",
|
"placeholder": "제목 입력",
|
||||||
"width": "300px",
|
"width": "300px",
|
||||||
"col": 1
|
"col": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "period",
|
|
||||||
"startDateId": "startDate",
|
|
||||||
"endDateId": "endDate",
|
|
||||||
"label": "조회 일자",
|
|
||||||
"col": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"id": "searchContent",
|
|
||||||
"label": "우편 내용",
|
|
||||||
"placeholder": "우편 내용(공백으로 구분)",
|
|
||||||
"width": "300px",
|
|
||||||
"col": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"id": "status",
|
"id": "status",
|
||||||
"label": "이벤트 상태",
|
"label": "상태",
|
||||||
"optionsRef": "eventStatus",
|
"optionsRef": "opMenuBannerStatus",
|
||||||
"col": 2
|
"col": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "period",
|
||||||
|
"startDateId": "startDate",
|
||||||
|
"endDateId": "endDate",
|
||||||
|
"label": "기간",
|
||||||
|
"col": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"apiInfo": {
|
"apiInfo": {
|
||||||
"functionName": "EventView",
|
"endpointName": "EventView",
|
||||||
"loadOnMount": true,
|
"loadOnMount": true,
|
||||||
"paramsMapping": [
|
"paramTransforms": [
|
||||||
"searchTitle",
|
|
||||||
"searchContent",
|
|
||||||
"status",
|
|
||||||
{"param": "startDate", "transform": "toISOString"},
|
{"param": "startDate", "transform": "toISOString"},
|
||||||
{"param": "endDate", "transform": "toISOString"},
|
{"param": "endDate", "transform": "toISOString"}
|
||||||
"orderBy",
|
|
||||||
"pageSize",
|
|
||||||
"currentPage"
|
|
||||||
],
|
],
|
||||||
"pageField": "currentPage",
|
"pageField": "page_no",
|
||||||
"pageSizeField": "pageSize",
|
"pageSizeField": "page_size",
|
||||||
"orderField": "orderBy"
|
"orderField": "orderBy"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
119
src/assets/data/pages/eventTable.json
Normal file
119
src/assets/data/pages/eventTable.json
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
{
|
||||||
|
"id": "eventTable",
|
||||||
|
"selection": {
|
||||||
|
"type": "single",
|
||||||
|
"idField": "id"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"countType": "total",
|
||||||
|
"orderType": "desc",
|
||||||
|
"pageType": "default",
|
||||||
|
"buttons": [
|
||||||
|
{
|
||||||
|
"id": "delete",
|
||||||
|
"text": "선택 삭제",
|
||||||
|
"theme": "line",
|
||||||
|
"disableWhen": "noSelection",
|
||||||
|
"requiredAuth": "worldEventDelete",
|
||||||
|
"action": "delete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "register",
|
||||||
|
"text": "통합 이벤트 등록",
|
||||||
|
"theme": "primary",
|
||||||
|
"requiredAuth": "worldEventUpdate",
|
||||||
|
"action": "regist"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"id": "checkbox",
|
||||||
|
"type": "checkbox",
|
||||||
|
"width": "40px",
|
||||||
|
"title": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "row_num",
|
||||||
|
"type": "text",
|
||||||
|
"width": "70px",
|
||||||
|
"title": "번호"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "status",
|
||||||
|
"type": "status",
|
||||||
|
"width": "120px",
|
||||||
|
"title": "상태",
|
||||||
|
"option_name": "opCommonStatus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "title",
|
||||||
|
"type": "text",
|
||||||
|
"title": "제목",
|
||||||
|
"width": "150px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "personal_event_action_id",
|
||||||
|
"type": "text",
|
||||||
|
"width": "150px",
|
||||||
|
"title": "개인제작 이벤트 모드"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "global_event_action_id",
|
||||||
|
"type": "text",
|
||||||
|
"width": "150px",
|
||||||
|
"title": "기여도 이벤트 모드"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "max_point",
|
||||||
|
"type": "text",
|
||||||
|
"width": "150px",
|
||||||
|
"title": "기여도 목표점수"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "start_dt",
|
||||||
|
"type": "date",
|
||||||
|
"width": "220px",
|
||||||
|
"title": "시작일(KST)",
|
||||||
|
"format": {
|
||||||
|
"type": "function",
|
||||||
|
"name": "convertKTC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "end_dt",
|
||||||
|
"type": "date",
|
||||||
|
"width": "220px",
|
||||||
|
"title": "종료일(KST)",
|
||||||
|
"format": {
|
||||||
|
"type": "function",
|
||||||
|
"name": "convertKTC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "detail",
|
||||||
|
"type": "button",
|
||||||
|
"width": "120px",
|
||||||
|
"title": "상세보기",
|
||||||
|
"text": "상세보기",
|
||||||
|
"action": {
|
||||||
|
"type": "modal",
|
||||||
|
"target": "detailModal",
|
||||||
|
"dataParam": {
|
||||||
|
"id": "id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "history",
|
||||||
|
"type": "button",
|
||||||
|
"width": "120px",
|
||||||
|
"title": "히스토리",
|
||||||
|
"text": "히스토리"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort": {
|
||||||
|
"defaultColumn": "row_num",
|
||||||
|
"defaultDirection": "desc"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
"searchType": "ID",
|
"searchType": "ID",
|
||||||
"searchData": "",
|
"searchData": "",
|
||||||
"historyType": "",
|
"historyType": "",
|
||||||
"startDate": "",
|
"startDate": "today",
|
||||||
"endDate": "",
|
"endDate": "today",
|
||||||
"orderBy": "DESC",
|
"orderBy": "DESC",
|
||||||
"pageSize": 50,
|
"pageSize": 50,
|
||||||
"currentPage": 1
|
"currentPage": 1
|
||||||
@@ -37,7 +37,10 @@
|
|||||||
"startDateId": "startDate",
|
"startDateId": "startDate",
|
||||||
"endDateId": "endDate",
|
"endDateId": "endDate",
|
||||||
"label": "기간",
|
"label": "기간",
|
||||||
"col": 2
|
"col": 2,
|
||||||
|
"width": "500px",
|
||||||
|
"format": "YYYY-MM-DD HH:mm:ss",
|
||||||
|
"showTime": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"id": "timestamp",
|
"id": "logTime",
|
||||||
"type": "date",
|
"type": "date",
|
||||||
"width": "200px",
|
"width": "200px",
|
||||||
"title": "일시(KST)",
|
"title": "일시(KST)",
|
||||||
@@ -22,25 +22,38 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dbType",
|
"id": "category",
|
||||||
"type": "option",
|
"type": "option",
|
||||||
"width": "100px",
|
"width": "100px",
|
||||||
"title": "DB타입",
|
"title": "로그종류",
|
||||||
"option_name": "opDBType"
|
"option_name": "opLogCategory"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "historyType",
|
"id": "action",
|
||||||
"type": "option",
|
"type": "option",
|
||||||
"width": "150px",
|
"width": "150px",
|
||||||
"title": "이력종류",
|
"title": "로그액션",
|
||||||
"option_name": "opHistoryType"
|
"option_name": "opLogAction"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "userId",
|
"id": "status",
|
||||||
|
"type": "option",
|
||||||
|
"width": "100px",
|
||||||
|
"title": "상태",
|
||||||
|
"option_name": "opCommonStatus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "worker",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"width": "100px",
|
"width": "100px",
|
||||||
"title": "작업자"
|
"title": "작업자"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "message",
|
||||||
|
"type": "text",
|
||||||
|
"width": "100px",
|
||||||
|
"title": "비고"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "detail",
|
"id": "detail",
|
||||||
"type": "button",
|
"type": "button",
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
49
src/assets/data/pages/metaCraftingSearch.json
Normal file
49
src/assets/data/pages/metaCraftingSearch.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"initialSearchParams": {
|
||||||
|
"search_type": "ID",
|
||||||
|
"search_data": "",
|
||||||
|
"small_type": "ALL",
|
||||||
|
"recipe_type": "ALL",
|
||||||
|
"orderBy": "DESC",
|
||||||
|
"pageSize": 50,
|
||||||
|
"currentPage": 1
|
||||||
|
},
|
||||||
|
|
||||||
|
"searchFields": [
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"id": "search_type",
|
||||||
|
"optionsRef": "itemSearchType",
|
||||||
|
"col": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "search_data",
|
||||||
|
"placeholder": "아이템 입력",
|
||||||
|
"width": "300px",
|
||||||
|
"col": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"id": "small_type",
|
||||||
|
"label": "제작 분류",
|
||||||
|
"optionsRef": "opPropSmallType",
|
||||||
|
"col": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"id": "recipe_type",
|
||||||
|
"label": "레시피 필요 여부",
|
||||||
|
"optionsRef": "opPropRecipeType",
|
||||||
|
"col": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"apiInfo": {
|
||||||
|
"endpointName": "getCraftingDictionaryList",
|
||||||
|
"loadOnMount": true,
|
||||||
|
"pageField": "page_no",
|
||||||
|
"pageSizeField": "page_size",
|
||||||
|
"orderField": "orderBy"
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/assets/data/pages/rankingSearch.json
Normal file
48
src/assets/data/pages/rankingSearch.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"initialSearchParams": {
|
||||||
|
"searchData": "",
|
||||||
|
"status": "ALL",
|
||||||
|
"startDate": "",
|
||||||
|
"endDate": "",
|
||||||
|
"orderBy": "DESC",
|
||||||
|
"pageSize": 50,
|
||||||
|
"currentPage": 1
|
||||||
|
},
|
||||||
|
|
||||||
|
"searchFields": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "searchData",
|
||||||
|
"label": "제목",
|
||||||
|
"placeholder": "제목 입력",
|
||||||
|
"width": "300px",
|
||||||
|
"col": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"id": "status",
|
||||||
|
"label": "상태",
|
||||||
|
"optionsRef": "opMenuBannerStatus",
|
||||||
|
"col": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "period",
|
||||||
|
"startDateId": "startDate",
|
||||||
|
"endDateId": "endDate",
|
||||||
|
"label": "기간",
|
||||||
|
"col": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"apiInfo": {
|
||||||
|
"endpointName": "RankingScheduleView",
|
||||||
|
"loadOnMount": true,
|
||||||
|
"paramTransforms": [
|
||||||
|
{"param": "startDate", "transform": "toISOString"},
|
||||||
|
{"param": "endDate", "transform": "toISOString"}
|
||||||
|
],
|
||||||
|
"pageField": "page_no",
|
||||||
|
"pageSizeField": "page_size",
|
||||||
|
"orderField": "orderBy"
|
||||||
|
}
|
||||||
|
}
|
||||||
140
src/assets/data/pages/rankingTable.json
Normal file
140
src/assets/data/pages/rankingTable.json
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
{
|
||||||
|
"id": "rankingTable",
|
||||||
|
"selection": {
|
||||||
|
"type": "single",
|
||||||
|
"idField": "id"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"countType": "total",
|
||||||
|
"orderType": "desc",
|
||||||
|
"pageType": "default",
|
||||||
|
"buttons": [
|
||||||
|
{
|
||||||
|
"id": "delete",
|
||||||
|
"text": "선택 삭제",
|
||||||
|
"theme": "line",
|
||||||
|
"disableWhen": "noSelection",
|
||||||
|
"requiredAuth": "rankingDelete",
|
||||||
|
"action": "delete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "register",
|
||||||
|
"text": "랭킹 스케줄 등록",
|
||||||
|
"theme": "primary",
|
||||||
|
"requiredAuth": "rankingUpdate",
|
||||||
|
"action": "regist"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"id": "checkbox",
|
||||||
|
"type": "checkbox",
|
||||||
|
"width": "40px",
|
||||||
|
"title": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "row_num",
|
||||||
|
"type": "text",
|
||||||
|
"width": "70px",
|
||||||
|
"title": "번호"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "status",
|
||||||
|
"type": "status",
|
||||||
|
"width": "100px",
|
||||||
|
"title": "상태",
|
||||||
|
"option_name": "opCommonStatus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "title",
|
||||||
|
"type": "text",
|
||||||
|
"title": "제목"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "meta_id",
|
||||||
|
"type": "text",
|
||||||
|
"title": "랭킹모드",
|
||||||
|
"width": "100px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event_action_id",
|
||||||
|
"type": "text",
|
||||||
|
"title": "이벤트 액션 그룹",
|
||||||
|
"width": "150px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "start_dt",
|
||||||
|
"type": "date",
|
||||||
|
"width": "200px",
|
||||||
|
"title": "시작일(KST)",
|
||||||
|
"format": {
|
||||||
|
"type": "function",
|
||||||
|
"name": "convertKTC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "end_dt",
|
||||||
|
"type": "date",
|
||||||
|
"width": "200px",
|
||||||
|
"title": "종료일(KST)",
|
||||||
|
"format": {
|
||||||
|
"type": "function",
|
||||||
|
"name": "convertKTC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "base_dt",
|
||||||
|
"type": "date",
|
||||||
|
"width": "200px",
|
||||||
|
"title": "기준일(KST)",
|
||||||
|
"format": {
|
||||||
|
"type": "function",
|
||||||
|
"name": "convertKTC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "refresh_interval",
|
||||||
|
"type": "text",
|
||||||
|
"width": "120px",
|
||||||
|
"title": "새로고침 주기"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "initialization_interval",
|
||||||
|
"type": "text",
|
||||||
|
"width": "120px",
|
||||||
|
"title": "초기화 주기"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "snapshot_interval",
|
||||||
|
"type": "text",
|
||||||
|
"width": "120px",
|
||||||
|
"title": "스냅샷 주기"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "detail",
|
||||||
|
"type": "button",
|
||||||
|
"width": "120px",
|
||||||
|
"title": "상세보기",
|
||||||
|
"text": "상세보기",
|
||||||
|
"action": {
|
||||||
|
"type": "modal",
|
||||||
|
"target": "detailModal",
|
||||||
|
"dataParam": {
|
||||||
|
"id": "id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "history",
|
||||||
|
"type": "button",
|
||||||
|
"width": "120px",
|
||||||
|
"title": "히스토리",
|
||||||
|
"text": "히스토리"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort": {
|
||||||
|
"defaultColumn": "row_num",
|
||||||
|
"defaultDirection": "desc"
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/assets/data/pages/rewardEventSearch.json
Normal file
63
src/assets/data/pages/rewardEventSearch.json
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"initialSearchParams": {
|
||||||
|
"searchTitle": "",
|
||||||
|
"searchContent": "",
|
||||||
|
"status": "ALL",
|
||||||
|
"startDate": "",
|
||||||
|
"endDate": "",
|
||||||
|
"orderBy": "DESC",
|
||||||
|
"pageSize": 50,
|
||||||
|
"currentPage": 1
|
||||||
|
},
|
||||||
|
|
||||||
|
"searchFields": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "searchTitle",
|
||||||
|
"label": "우편 제목",
|
||||||
|
"placeholder": "제목 입력",
|
||||||
|
"width": "300px",
|
||||||
|
"col": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "period",
|
||||||
|
"startDateId": "startDate",
|
||||||
|
"endDateId": "endDate",
|
||||||
|
"label": "조회 일자",
|
||||||
|
"col": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "searchContent",
|
||||||
|
"label": "우편 내용",
|
||||||
|
"placeholder": "우편 내용(공백으로 구분)",
|
||||||
|
"width": "300px",
|
||||||
|
"col": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"id": "status",
|
||||||
|
"label": "이벤트 상태",
|
||||||
|
"optionsRef": "eventStatus",
|
||||||
|
"col": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"apiInfo": {
|
||||||
|
"functionName": "RewardEventView",
|
||||||
|
"loadOnMount": true,
|
||||||
|
"paramsMapping": [
|
||||||
|
"searchTitle",
|
||||||
|
"searchContent",
|
||||||
|
"status",
|
||||||
|
{"param": "startDate", "transform": "toISOString"},
|
||||||
|
{"param": "endDate", "transform": "toISOString"},
|
||||||
|
"orderBy",
|
||||||
|
"pageSize",
|
||||||
|
"currentPage"
|
||||||
|
],
|
||||||
|
"pageField": "currentPage",
|
||||||
|
"pageSizeField": "pageSize",
|
||||||
|
"orderField": "orderBy"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,6 +52,16 @@ export const authType = {
|
|||||||
menuBannerRead: 50,
|
menuBannerRead: 50,
|
||||||
menuBannerUpdate: 51,
|
menuBannerUpdate: 51,
|
||||||
menuBannerDelete: 52,
|
menuBannerDelete: 52,
|
||||||
|
itemDictionaryRead: 53,
|
||||||
|
rankManagerRead: 54,
|
||||||
|
rankManagerUpdate: 55,
|
||||||
|
rankingRead: 56,
|
||||||
|
rankingUpdate: 57,
|
||||||
|
rankingDelete: 58,
|
||||||
|
worldEventRead: 59,
|
||||||
|
worldEventUpdate: 60,
|
||||||
|
worldEventDelete: 61,
|
||||||
|
craftingDictionaryRead: 62,
|
||||||
|
|
||||||
|
|
||||||
levelReader: 999,
|
levelReader: 999,
|
||||||
@@ -75,20 +85,6 @@ export const adminAuthLevel = {
|
|||||||
DEVELOPER: "Developer",
|
DEVELOPER: "Developer",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TabUserList = [
|
|
||||||
{ title: '기본정보' },
|
|
||||||
{ title: '아바타' },
|
|
||||||
{ title: '의상' },
|
|
||||||
{ title: '도구' },
|
|
||||||
{ title: '인벤토리' },
|
|
||||||
{ title: '우편' },
|
|
||||||
{ title: '마이홈' },
|
|
||||||
{ title: '친구목록' },
|
|
||||||
{ title: '타투' },
|
|
||||||
{ title: '퀘스트' },
|
|
||||||
// { title: '클레임' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ivenTabType = {
|
export const ivenTabType = {
|
||||||
CLOTH: "cloth",
|
CLOTH: "cloth",
|
||||||
PROP: "prop",
|
PROP: "prop",
|
||||||
@@ -161,7 +157,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;
|
||||||
@@ -93,12 +93,6 @@ const UserInfoTable = styled.table`
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
tr:first-child {
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
border-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
th,
|
th,
|
||||||
td {
|
td {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
|
|||||||
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} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
153
src/components/DataManage/UserSnapshotLogContent.js
Normal file
153
src/components/DataManage/UserSnapshotLogContent.js
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import React, { Fragment, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CircularProgressWrapper,
|
||||||
|
FormWrapper,
|
||||||
|
TableStyle,
|
||||||
|
TableWrapper,
|
||||||
|
} from '../../styles/Components';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
import { UserSnapshotLogSearchBar, useUserSnapshotLogSearch } from '../searchBar';
|
||||||
|
import { TopButton, ViewTableInfo } from '../common';
|
||||||
|
import Pagination from '../common/Pagination/Pagination';
|
||||||
|
import {
|
||||||
|
INITIAL_PAGE_LIMIT,
|
||||||
|
} 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 UserSnapshotLogContent = ({ 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
|
||||||
|
} = useUserSnapshotLogSearch(token, 500);
|
||||||
|
|
||||||
|
const tableHeaders = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ id: 'logDay', label: '일자', width: '80px' },
|
||||||
|
{ id: 'accountId', label: 'account ID', width: '80px' },
|
||||||
|
{ id: 'userGuid', label: 'GUID', width: '180px' },
|
||||||
|
{ id: 'userNickname', label: '아바타명', width: '150px' },
|
||||||
|
{ id: 'gold', label: '골드', width: '80px' },
|
||||||
|
{ id: 'sapphire', label: '사파이어', width: '80px' },
|
||||||
|
{ id: 'calium', label: '칼리움', width: '80px' },
|
||||||
|
{ id: 'ruby', label: '루비', width: '80px' },
|
||||||
|
{ id: 'item_13080002', label: '퀘스트 메달', width: '80px' },
|
||||||
|
{ id: 'item_13080004', label: '보급품 메달', width: '80px' },
|
||||||
|
{ id: 'item_13080005', label: '제작 메달', width: '80px' },
|
||||||
|
{ id: 'item_13080006', label: '에테론 315 포드', width: '80px' },
|
||||||
|
{ id: 'item_13080007', label: '에테론 316 포드', width: '80px' },
|
||||||
|
{ id: 'item_13080008', label: '에테론 317 포드', width: '80px' },
|
||||||
|
{ id: 'item_13080009', label: '에테론 318 포드', width: '80px' },
|
||||||
|
{ id: 'item_11570001', label: '강화잉크', width: '80px' },
|
||||||
|
{ id: 'item_11570002', label: '연성잉크', width: '80px' },
|
||||||
|
{ id: 'lastLogoutTime', label: '마지막 로그아웃 일자', width: '150px' },
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if(!active) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedPageWrapper>
|
||||||
|
<FormWrapper>
|
||||||
|
<UserSnapshotLogSearchBar
|
||||||
|
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="GameUserSnapshotLogExport"
|
||||||
|
params={searchParams}
|
||||||
|
fileName={t('FILE_GAME_LOG_USER_SNAPSHOT')}
|
||||||
|
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?.snapshot_list?.map((item, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
<tr>
|
||||||
|
<td>{item.logDay}</td>
|
||||||
|
<td>{item.accountId}</td>
|
||||||
|
<td>{item.userGuid}</td>
|
||||||
|
<td>{item.userNickname}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.gold)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.sapphire)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.calium)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.ruby)}</td>
|
||||||
|
<td>{item.item_13080002}</td>
|
||||||
|
<td>{item.item_13080004}</td>
|
||||||
|
<td>{item.item_13080005}</td>
|
||||||
|
<td>{item.item_13080006}</td>
|
||||||
|
<td>{item.item_13080007}</td>
|
||||||
|
<td>{item.item_13080008}</td>
|
||||||
|
<td>{item.item_13080009}</td>
|
||||||
|
<td>{item.item_11570001}</td>
|
||||||
|
<td>{item.item_11570002}</td>
|
||||||
|
<td>{item.lastLogoutTime}</td>
|
||||||
|
</tr>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</TableStyle>
|
||||||
|
</TableWrapper>
|
||||||
|
{dataList?.snapshot_list &&
|
||||||
|
<Pagination
|
||||||
|
postsPerPage={searchParams.page_size}
|
||||||
|
totalPosts={dataList?.total_all}
|
||||||
|
setCurrentPage={handlePageChange}
|
||||||
|
currentPage={searchParams.page_no}
|
||||||
|
pageLimit={INITIAL_PAGE_LIMIT}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<TopButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</AnimatedPageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default UserSnapshotLogContent;
|
||||||
@@ -4,7 +4,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { UserTattooView } from '../../apis/Users';
|
import { UserTattooView } from '../../apis/Users';
|
||||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
|
||||||
const UserTatttooInfo = ({ userInfo }) => {
|
const UserTattooInfo = ({ userInfo }) => {
|
||||||
const [dataList, setDataList] = useState();
|
const [dataList, setDataList] = useState();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ const UserTatttooInfo = ({ userInfo }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UserTatttooInfo;
|
export default UserTattooInfo;
|
||||||
|
|
||||||
const UserDefaultTable = styled.table`
|
const UserDefaultTable = styled.table`
|
||||||
border: 1px solid #e8eaec;
|
border: 1px solid #e8eaec;
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ import {STORAGE_GAME_LOG_CURRENCY_SEARCH, } from '../../assets/data/adminConstan
|
|||||||
import ExcelExportButton from '../common/button/ExcelExportButton';
|
import ExcelExportButton from '../common/button/ExcelExportButton';
|
||||||
import CircularProgress from '../common/CircularProgress';
|
import CircularProgress from '../common/CircularProgress';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import CurrencyIndexSearchBar from '../searchBar/CurrencyIndexSearchBar';
|
import CurrencyUserIndexSearchBar from '../searchBar/CurrencyUserIndexSearchBar';
|
||||||
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,9 +130,9 @@ const CreditContent = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<CurrencyIndexSearchBar
|
<CurrencyUserIndexSearchBar
|
||||||
searchParams={searchParams}
|
searchParams={searchParams}
|
||||||
onSearch={(newParams, executeSearch = true) => {
|
onSearch={(newParams, executeSearch = true) => {
|
||||||
if (executeSearch) {
|
if (executeSearch) {
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
126
src/components/IndexManage/CurrencyAcquireContent.js
Normal file
126
src/components/IndexManage/CurrencyAcquireContent.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import React, { Fragment, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TableStyle,
|
||||||
|
FormWrapper,
|
||||||
|
TableWrapper, ListOption,
|
||||||
|
} from '../../styles/Components';
|
||||||
|
|
||||||
|
import { useCurrencyAcquireIndexSearch, CurrencyAcquireIndexSearchBar } from '../searchBar';
|
||||||
|
import { TopButton, ViewTableInfo } from '../common';
|
||||||
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
import { numberFormatter } from '../../utils';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
|
import CSVDownloadButton from '../common/button/CsvDownButton';
|
||||||
|
|
||||||
|
const CurrencyAcquireContent = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
const tableRef = useRef(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
searchParams,
|
||||||
|
loading: dataLoading,
|
||||||
|
data: dataList,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
updateSearchParams
|
||||||
|
} = useCurrencyAcquireIndexSearch(token);
|
||||||
|
|
||||||
|
const tableHeaders = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ id: 'logDay', label: '일자', width: '100px' },
|
||||||
|
{ id: 'mail', label: '우편', width: '80px' },
|
||||||
|
{ id: 'ShopSell', label: '상점 판매', width: '80px' },
|
||||||
|
{ id: 'ShopPurchase', label: '상점 구매', width: '80px' },
|
||||||
|
{ id: 'seasonPass', label: '시즌 패스', width: '80px' },
|
||||||
|
{ id: 'claim', label: '클레임', width: '80px' },
|
||||||
|
{ id: 'quest', label: '퀘스트', width: '80px' },
|
||||||
|
{ id: 'ugq', label: 'UGQ', width: '80px' },
|
||||||
|
{ id: 'battleObject', label: '보급품 상자', width: '80px' },
|
||||||
|
{ id: 'randomBox', label: '랜덤박스', width: '80px' },
|
||||||
|
{ id: 'landRent', label: '랜드 임대', width: '80px' },
|
||||||
|
{ id: 'caliumExchange', label: '칼리움 교환소', width: '80px' },
|
||||||
|
{ id: 'caliumConverter', label: '칼리움 컨버터', width: '80px' },
|
||||||
|
{ id: 'beaconShop', label: '비컨 상점', width: '80px' },
|
||||||
|
{ id: 'etc', label: '기타', width: '80px' },
|
||||||
|
{ id: 'summary', label: '합계', width: '80px' },
|
||||||
|
];
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedPageWrapper>
|
||||||
|
<FormWrapper>
|
||||||
|
<CurrencyAcquireIndexSearchBar
|
||||||
|
searchParams={searchParams}
|
||||||
|
onSearch={(newParams, executeSearch = true) => {
|
||||||
|
if (executeSearch) {
|
||||||
|
handleSearch(newParams);
|
||||||
|
} else {
|
||||||
|
updateSearchParams(newParams);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReset={handleReset}
|
||||||
|
/>
|
||||||
|
</FormWrapper>
|
||||||
|
<ViewTableInfo >
|
||||||
|
<ListOption>
|
||||||
|
<CSVDownloadButton tableRef={tableRef} fileName={t('FILE_INDEX_CURRENCY_ACQUIRE')} />
|
||||||
|
</ListOption>
|
||||||
|
</ViewTableInfo>
|
||||||
|
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||||
|
<>
|
||||||
|
<TableWrapper>
|
||||||
|
<TableStyle ref={tableRef}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{tableHeaders.map(header => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
width={header.width}
|
||||||
|
colSpan={header.colSpan}
|
||||||
|
>
|
||||||
|
{header.label}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{dataList?.currency_list?.map((item, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
<tr>
|
||||||
|
<td>{item.logDay}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.MailTaken)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ShopSell)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ShopPurchase)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.SeasonPassTakeReward)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ClaimReward)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.QuestMainReward || 0) + (item.actionSummary.QuestTaskUpdate || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.UgqAbort)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.RewardProp)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ItemRandomBoxUse)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.GainLandProfit)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ConvertExchangeCalium)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ConvertCalium)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.BeaconSell || 0) + (item.actionSummary.BeaconShopReceivePaymentForSales || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.MoneyChange)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.totalDeltaAmount)}</td>
|
||||||
|
</tr>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</TableStyle>
|
||||||
|
</TableWrapper>
|
||||||
|
<TopButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</AnimatedPageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CurrencyAcquireContent;
|
||||||
109
src/components/IndexManage/CurrencyAssetsContent.js
Normal file
109
src/components/IndexManage/CurrencyAssetsContent.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import React, { Fragment, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TableStyle,
|
||||||
|
FormWrapper,
|
||||||
|
TableWrapper, ListOption
|
||||||
|
} from '../../styles/Components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AssetsIndexSearchBar, useAssetsIndexSearch,
|
||||||
|
} from '../searchBar';
|
||||||
|
import { TopButton, ViewTableInfo } from '../common';
|
||||||
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
import { numberFormatter } from '../../utils';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
|
import CSVDownloadButton from '../common/button/CsvDownButton';
|
||||||
|
import DailyDashBoard from './DailyCaliumDashBoard';
|
||||||
|
|
||||||
|
const CurrencyAssetsContent = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
const tableRef = useRef(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
searchParams,
|
||||||
|
loading: dataLoading,
|
||||||
|
data: dataList,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
updateSearchParams
|
||||||
|
} = useAssetsIndexSearch(token);
|
||||||
|
|
||||||
|
const tableHeaders = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ id: 'logDay', label: '일자', width: '100px' },
|
||||||
|
{ id: 'userCount', label: '유저수', width: '100px' },
|
||||||
|
{ id: 'gold', label: '골드', width: '100px' },
|
||||||
|
{ id: 'sapphire', label: '사파이어', width: '100px' },
|
||||||
|
{ id: 'calium', label: '칼리움', width: '100px' },
|
||||||
|
{ id: 'ruby', label: '루비', width: '100px' }
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedPageWrapper>
|
||||||
|
<DailyDashBoard />
|
||||||
|
<FormWrapper>
|
||||||
|
<AssetsIndexSearchBar
|
||||||
|
searchParams={searchParams}
|
||||||
|
onSearch={(newParams, executeSearch = true) => {
|
||||||
|
if (executeSearch) {
|
||||||
|
handleSearch(newParams);
|
||||||
|
} else {
|
||||||
|
updateSearchParams(newParams);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReset={handleReset}
|
||||||
|
/>
|
||||||
|
</FormWrapper>
|
||||||
|
<ViewTableInfo >
|
||||||
|
<ListOption>
|
||||||
|
<CSVDownloadButton tableRef={tableRef} fileName={t('FILE_INDEX_ASSETS_CURRENCY')} />
|
||||||
|
</ListOption>
|
||||||
|
</ViewTableInfo>
|
||||||
|
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||||
|
<>
|
||||||
|
<TableWrapper>
|
||||||
|
<TableStyle ref={tableRef}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{tableHeaders.map(header => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
width={header.width}
|
||||||
|
colSpan={header.colSpan}
|
||||||
|
>
|
||||||
|
{header.label}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{dataList?.assets_list?.map((data, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
<tr>
|
||||||
|
<td>{data.logDay}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.userCount)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.gold)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.sapphire)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.calium)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.ruby)}</td>
|
||||||
|
</tr>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</TableStyle>
|
||||||
|
</TableWrapper>
|
||||||
|
<TopButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</AnimatedPageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CurrencyAssetsContent;
|
||||||
128
src/components/IndexManage/CurrencyConsumeContent.js
Normal file
128
src/components/IndexManage/CurrencyConsumeContent.js
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import React, { Fragment, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TableStyle,
|
||||||
|
FormWrapper,
|
||||||
|
TableWrapper, ListOption,
|
||||||
|
} from '../../styles/Components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useCurrencyConsumeIndexSearch, CurrencyConsumeIndexSearchBar,
|
||||||
|
} from '../searchBar';
|
||||||
|
import { TopButton, ViewTableInfo } from '../common';
|
||||||
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
import { numberFormatter } from '../../utils';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
|
import CSVDownloadButton from '../common/button/CsvDownButton';
|
||||||
|
|
||||||
|
const CurrencyConsumeContent = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
const tableRef = useRef(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
searchParams,
|
||||||
|
loading: dataLoading,
|
||||||
|
data: dataList,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
updateSearchParams
|
||||||
|
} = useCurrencyConsumeIndexSearch(token);
|
||||||
|
|
||||||
|
const tableHeaders = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ id: 'logDay', label: '일자', width: '100px' },
|
||||||
|
{ id: 'itemBuy', label: '아이템 구매', width: '80px' },
|
||||||
|
{ id: 'ShopPurchase', label: '상점 구매', width: '80px' },
|
||||||
|
{ id: 'ShopRePurchase', label: '상점 재구매', width: '80px' },
|
||||||
|
{ id: 'beaconShop', label: '비컨상점', width: '80px' },
|
||||||
|
{ id: 'beacon', label: '비컨', width: '80px' },
|
||||||
|
{ id: 'taxi', label: '택시', width: '80px' },
|
||||||
|
{ id: 'farming', label: '파밍', width: '80px' },
|
||||||
|
{ id: 'seasonPass', label: '시즌 패스', width: '80px' },
|
||||||
|
{ id: 'caliumExchange', label: '칼리움 교환소', width: '80px' },
|
||||||
|
{ id: 'caliumConverter', label: '칼리움 컨버터', width: '80px' },
|
||||||
|
{ id: 'rent', label: '랜드 렌탈', width: '80px' },
|
||||||
|
{ id: 'landAuction', label: '랜드 경매', width: '80px' },
|
||||||
|
{ id: 'ugq', label: 'UGQ', width: '80px' },
|
||||||
|
{ id: 'etc', label: '기타', width: '80px' },
|
||||||
|
{ id: 'summary', label: '합계', width: '80px' },
|
||||||
|
];
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedPageWrapper>
|
||||||
|
<FormWrapper>
|
||||||
|
<CurrencyConsumeIndexSearchBar
|
||||||
|
searchParams={searchParams}
|
||||||
|
onSearch={(newParams, executeSearch = true) => {
|
||||||
|
if (executeSearch) {
|
||||||
|
handleSearch(newParams);
|
||||||
|
} else {
|
||||||
|
updateSearchParams(newParams);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReset={handleReset}
|
||||||
|
/>
|
||||||
|
</FormWrapper>
|
||||||
|
<ViewTableInfo >
|
||||||
|
<ListOption>
|
||||||
|
<CSVDownloadButton tableRef={tableRef} fileName={t('FILE_INDEX_CURRENCY_CONSUME')} />
|
||||||
|
</ListOption>
|
||||||
|
</ViewTableInfo>
|
||||||
|
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||||
|
<>
|
||||||
|
<TableWrapper>
|
||||||
|
<TableStyle ref={tableRef}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{tableHeaders.map(header => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
width={header.width}
|
||||||
|
colSpan={header.colSpan}
|
||||||
|
>
|
||||||
|
{header.label}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{dataList?.currency_list?.map((item, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
<tr>
|
||||||
|
<td>{item.logDay}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ItemBuy)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ShopPurchase)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ShopRePurchase)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.BeaconShopRegisterItem || 0) + (item.actionSummary.BeaconShopPurchaseItem || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.BeaconCreate || 0) + (item.actionSummary.BeaconEdit || 0) + (item.actionSummary.BeaconAppearanceCustomize || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.TaxiMove)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.FarmingStart)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.SeasonPassBuyCharged)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ConvertExchangeCalium)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ConvertCalium)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.RentFloor)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.LandAuctionBid)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.UgqAssign)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.MoneyChange ||0) + (item.actionSummary.RenewalShopProducts ||0) + (item.actionSummary.CharacterAppearanceCustomize ||0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.totalDeltaAmount)}</td>
|
||||||
|
</tr>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</TableStyle>
|
||||||
|
</TableWrapper>
|
||||||
|
<TopButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</AnimatedPageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CurrencyConsumeContent;
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
|
||||||
|
|
||||||
import { TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
|
||||||
import { DailySearchBar } from '../../components/IndexManage/index';
|
|
||||||
import { DailyActiveUserExport, DailyActiveUserView } from '../../apis';
|
|
||||||
|
|
||||||
const PlayTimeContent = () => {
|
|
||||||
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();
|
|
||||||
|
|
||||||
const [dataList, setDataList] = useState([]);
|
|
||||||
const [resultData, setResultData] = useState([]);
|
|
||||||
|
|
||||||
const [sendDate, setSendDate] = useState(START_DATE);
|
|
||||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData(START_DATE, END_DATE);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// DAU 데이터
|
|
||||||
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());
|
|
||||||
|
|
||||||
// await DailyActiveUserView(token, startDateToLocal, endDateToLocal).then(data => {
|
|
||||||
// console.log(data);
|
|
||||||
// setDataList(data);
|
|
||||||
// });
|
|
||||||
|
|
||||||
setSendDate(startDateToLocal);
|
|
||||||
setFinishDate(endDateToLocal);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 검색 함수
|
|
||||||
const handleSearch = (send_dt, end_dt) => {
|
|
||||||
fetchData(send_dt, end_dt);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 엑셀 다운로드
|
|
||||||
const handleXlsxExport = () => {
|
|
||||||
const fileName = 'Caliverse_Dau.xlsx';
|
|
||||||
DailyActiveUserExport(token, fileName, sendDate, finishDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DailySearchBar setResultData={setResultData} resultData={resultData} handleSearch={handleSearch} fetchData={fetchData} />
|
|
||||||
<TableInfo>
|
|
||||||
<ListOption>
|
|
||||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
|
||||||
</ListOption>
|
|
||||||
</TableInfo>
|
|
||||||
<IndexTableWrap>
|
|
||||||
<TableStyle>
|
|
||||||
<caption></caption>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th rowSpan="1" width="45">
|
|
||||||
일자
|
|
||||||
</th>
|
|
||||||
<th colSpan="1" width="30">
|
|
||||||
DAU
|
|
||||||
</th>
|
|
||||||
{/*<th colSpan="1" width="30">*/}
|
|
||||||
{/* DALC*/}
|
|
||||||
{/*</th>*/}
|
|
||||||
<th colSpan="1" width="30">
|
|
||||||
DGLC
|
|
||||||
</th>
|
|
||||||
{/*<th colSpan="1" width="30">*/}
|
|
||||||
{/* MaxAU*/}
|
|
||||||
{/*</th>*/}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
|
|
||||||
{dataList && (dataList || []).map((data, index) => (
|
|
||||||
<tr key={index}>
|
|
||||||
<td>{data.date}</td>
|
|
||||||
<td>{data.dau}</td>
|
|
||||||
{/*<td>{data.dalc}</td>*/}
|
|
||||||
<td>{data.dglc}</td>
|
|
||||||
{/*<td>{data.maxAu}</td>*/}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</TableStyle>
|
|
||||||
</IndexTableWrap>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PlayTimeContent;
|
|
||||||
|
|
||||||
141
src/components/IndexManage/DailyCaliumDashBoard.js
Normal file
141
src/components/IndexManage/DailyCaliumDashBoard.js
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { dashboardCaliumIndex } from '../../apis';
|
||||||
|
|
||||||
|
import { styled } from 'styled-components';
|
||||||
|
import TitleArrow from '../../assets/img/icon/icon-title.png';
|
||||||
|
|
||||||
|
const DailyDashBoard = () => {
|
||||||
|
const [boardState, setBoardState] = useState('active');
|
||||||
|
const [totalData, setTotalData] = useState([]);
|
||||||
|
|
||||||
|
const handleBoard = () => {
|
||||||
|
if (boardState === 'active') {
|
||||||
|
setBoardState('inactive');
|
||||||
|
} else {
|
||||||
|
setBoardState('active');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
await dashboardCaliumIndex(token).then(data => {
|
||||||
|
setTotalData(data.dashboard_calium);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const dashboardItems = [
|
||||||
|
{
|
||||||
|
title: '컨버터 칼리움 보유량',
|
||||||
|
value: totalData.total_calium || 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '컨버터 변환 효율',
|
||||||
|
value: `${totalData.converter_rate || 0}%`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '인플레이션 가중치',
|
||||||
|
value: `${totalData.inflation_rate || 0}%`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '칼리움 누적 총량',
|
||||||
|
value: totalData.cumulative_calium || 0
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DailyBoardWrapper>
|
||||||
|
{totalData &&
|
||||||
|
<DailyBoard>
|
||||||
|
<BoardTitle onClick={handleBoard} $state={boardState}>
|
||||||
|
Daily Dashboard
|
||||||
|
</BoardTitle>
|
||||||
|
<BoardInfo $state={boardState}>
|
||||||
|
<BoxWrapper>
|
||||||
|
{dashboardItems?.map((item, index) => (
|
||||||
|
<InfoItem key={index}>
|
||||||
|
<InfoTitle>{item.title}</InfoTitle>
|
||||||
|
<InfoValue>
|
||||||
|
{item.value}
|
||||||
|
</InfoValue>
|
||||||
|
</InfoItem>
|
||||||
|
))}
|
||||||
|
</BoxWrapper>
|
||||||
|
</BoardInfo>
|
||||||
|
</DailyBoard>
|
||||||
|
}
|
||||||
|
</DailyBoardWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DailyDashBoard;
|
||||||
|
|
||||||
|
const DailyBoardWrapper = styled.div`
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DailyBoard = styled.div`
|
||||||
|
background: #f6f6f6;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BoardTitle = styled.div`
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 52px;
|
||||||
|
padding: 0 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 11px;
|
||||||
|
height: 52px;
|
||||||
|
margin-left: 10px;
|
||||||
|
background: url(${TitleArrow}) 50% 50% no-repeat;
|
||||||
|
position: absolute;
|
||||||
|
transform: ${props => (props.$state === 'active' ? 'rotate(0)' : 'rotate(180deg)')};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BoardInfo = styled.div`
|
||||||
|
padding: 20px;
|
||||||
|
border-top: 1px solid #d9d9d9;
|
||||||
|
display: ${props => (props.$state === 'active' ? 'block' : 'none')};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BoxWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InfoItem = styled.div`
|
||||||
|
width: 18%;
|
||||||
|
background: #fff;
|
||||||
|
padding: 15px 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 15px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InfoTitle = styled.div`
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InfoValue = styled.div`
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 5px 0;
|
||||||
|
gap: 5px 0;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
`;
|
||||||
@@ -26,7 +26,6 @@ const DailyDashBoard = ({ content }) => {
|
|||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
await userTotalIndex(token).then(data => {
|
await userTotalIndex(token).then(data => {
|
||||||
console.log(data);
|
|
||||||
setTotalData(data.dashboard);
|
setTotalData(data.dashboard);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
import { Fragment, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import Button from '../common/button/Button';
|
|
||||||
|
|
||||||
import { TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
|
||||||
import { DailySearchBar } from './index';
|
|
||||||
import { DailyMedalView } from '../../apis';
|
|
||||||
|
|
||||||
const DailyMedalContent = () => {
|
|
||||||
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();
|
|
||||||
|
|
||||||
const [dataList, setDataList] = useState([]);
|
|
||||||
const [resultData, setResultData] = useState([]);
|
|
||||||
|
|
||||||
const [sendDate, setSendDate] = useState(START_DATE);
|
|
||||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData(START_DATE, END_DATE);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
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 DailyMedalView(token, startDateToLocal, endDateToLocal));
|
|
||||||
|
|
||||||
setSendDate(startDateToLocal);
|
|
||||||
setFinishDate(endDateToLocal);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 검색 함수
|
|
||||||
const handleSearch = (send_dt, end_dt) => {
|
|
||||||
fetchData(send_dt, end_dt);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 엑셀 다운로드
|
|
||||||
const handleXlsxExport = () => {
|
|
||||||
const fileName = 'Caliverse_Daily_Medal.xlsx';
|
|
||||||
//DailyActiveUserExport(token, fileName, sendDate, finishDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DailySearchBar setResultData={setResultData} resultData={resultData} handleSearch={handleSearch} fetchData={fetchData} />
|
|
||||||
<TableInfo>
|
|
||||||
<ListOption>
|
|
||||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
|
||||||
</ListOption>
|
|
||||||
</TableInfo>
|
|
||||||
<IndexTableWrap>
|
|
||||||
<TableStyle>
|
|
||||||
<caption></caption>
|
|
||||||
<thead >
|
|
||||||
<tr>
|
|
||||||
<th rowSpan="1" width="20">
|
|
||||||
일자
|
|
||||||
</th>
|
|
||||||
<th colSpan="1" width="30">
|
|
||||||
UserID
|
|
||||||
</th>
|
|
||||||
<th colSpan="1" width="30">
|
|
||||||
닉네임
|
|
||||||
</th>
|
|
||||||
<th colSpan="1" width="30">
|
|
||||||
Item ID
|
|
||||||
</th>
|
|
||||||
<th colSpan="1" width="30">
|
|
||||||
획득량
|
|
||||||
</th>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
|
|
||||||
{(dataList || []).map((data, index) => (
|
|
||||||
<tr key={index}>
|
|
||||||
<td>{data.date}</td>
|
|
||||||
<td>{data.dau}</td>
|
|
||||||
<td>{data.dalc}</td>
|
|
||||||
<td>{data.dglc}</td>
|
|
||||||
<td>{data.maxAu}</td>
|
|
||||||
{Array.from({ length: 24 }, (_, i) => (
|
|
||||||
<td key={i}>{data['h' + i]}</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</TableStyle>
|
|
||||||
</IndexTableWrap>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DailyMedalContent;
|
|
||||||
|
|
||||||
152
src/components/IndexManage/ItemAcquireContent.js
Normal file
152
src/components/IndexManage/ItemAcquireContent.js
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import React, { Fragment, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TableStyle,
|
||||||
|
FormWrapper,
|
||||||
|
TableWrapper, ListOption, TextInput, TableInfoContent, Notice,
|
||||||
|
} from '../../styles/Components';
|
||||||
|
|
||||||
|
import { ItemAcquireIndexSearchBar, useItemAcquireIndexSearch } from '../searchBar';
|
||||||
|
import { TopButton, ViewTableInfo } from '../common';
|
||||||
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
import { numberFormatter } from '../../utils';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
|
import CSVDownloadButton from '../common/button/CsvDownButton';
|
||||||
|
|
||||||
|
const ItemAcquireContent = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
const tableRef = useRef(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
searchParams,
|
||||||
|
loading: dataLoading,
|
||||||
|
data: dataList,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
updateSearchParams
|
||||||
|
} = useItemAcquireIndexSearch(token);
|
||||||
|
|
||||||
|
const tableHeaders = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ id: 'logDay', label: '일자', width: '100px' },
|
||||||
|
{ id: 'mail', label: '우편', width: '80px' },
|
||||||
|
{ id: 'shopPurchase', label: '상점 구매', width: '80px' },
|
||||||
|
{ id: 'shopRePurchase', label: '상점 재구매', width: '80px' },
|
||||||
|
{ id: 'itemBuy', label: '아이템 구매', width: '80px' },
|
||||||
|
{ id: 'itemUse', label: '아이템 사용', width: '80px' },
|
||||||
|
{ id: 'seasonPass', label: '시즌 패스', width: '80px' },
|
||||||
|
{ id: 'claim', label: '클레임', width: '80px' },
|
||||||
|
{ id: 'quest', label: '퀘스트', width: '80px' },
|
||||||
|
{ id: 'ugq', label: 'UGQ', width: '80px' },
|
||||||
|
{ id: 'battleObject', label: '배틀맵', width: '80px' },
|
||||||
|
{ id: 'runRace', label: '런레이스', width: '80px' },
|
||||||
|
{ id: 'prop', label: '보급품 상자', width: '80px' },
|
||||||
|
{ id: 'randomBox', label: '랜덤박스', width: '80px' },
|
||||||
|
{ id: 'beacon', label: '비컨', width: '80px' },
|
||||||
|
{ id: 'beaconShop', label: '비컨 상점', width: '80px' },
|
||||||
|
{ id: 'myHome', label: '마이홈', width: '80px' },
|
||||||
|
{ id: 'craft', label: '크래프트', width: '80px' },
|
||||||
|
{ id: 'etc', label: '기타', width: '80px' },
|
||||||
|
{ id: 'summary', label: '합계', width: '80px' },
|
||||||
|
];
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedPageWrapper>
|
||||||
|
<FormWrapper>
|
||||||
|
<ItemAcquireIndexSearchBar
|
||||||
|
searchParams={searchParams}
|
||||||
|
onSearch={(newParams, executeSearch = true) => {
|
||||||
|
if (executeSearch) {
|
||||||
|
handleSearch(newParams);
|
||||||
|
} else {
|
||||||
|
updateSearchParams(newParams);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReset={handleReset}
|
||||||
|
/>
|
||||||
|
</FormWrapper>
|
||||||
|
<ViewTableInfo >
|
||||||
|
{dataList?.item_list && dataList.item_list.length > 0 &&
|
||||||
|
<TableInfoContent>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
value={dataList.item_list[0].itemId}
|
||||||
|
width="100px"
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
value={dataList.item_list[0].itemName}
|
||||||
|
width="150px"
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<Notice $color='#F15F5F'>* 확인되지 않은 액션이 있을 수 있습니다</Notice>
|
||||||
|
</TableInfoContent>
|
||||||
|
}
|
||||||
|
<ListOption>
|
||||||
|
<CSVDownloadButton tableRef={tableRef} fileName={t('FILE_INDEX_ITEM_ACQUIRE')} />
|
||||||
|
</ListOption>
|
||||||
|
</ViewTableInfo>
|
||||||
|
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||||
|
<>
|
||||||
|
<TableWrapper>
|
||||||
|
<TableStyle ref={tableRef}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{tableHeaders.map(header => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
width={header.width}
|
||||||
|
colSpan={header.colSpan}
|
||||||
|
>
|
||||||
|
{header.label}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{dataList?.item_list?.map((item, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
<tr>
|
||||||
|
<td>{item.logDay}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.MailTaken)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ShopPurchase)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ShopRePurchase)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ItemBuy)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ItemUse)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.SeasonPassTakeReward)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ClaimReward)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.QuestMainReward || 0) + (item.actionSummary.QuestTaskUpdate || 0) + (item.actionSummary.QuestMainTask || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.UgqAbort)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.BattleRoundStateUpdate || 0) + (item.actionSummary.BattlePodCombatOccupyReward || 0) + (item.actionSummary.BattleObjectInteraction || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.RunRaceFinishReward || 0) + (item.actionSummary.RunRaceRespawnReward || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.RewardProp)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ItemRandomBoxUse)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.BeaconCreate || 0) + (item.actionSummary.BeaconEdit || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.BeaconShopPurchaseItem || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.DeleteMyhome || 0) + (item.actionSummary.SaveMyhome || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.CraftFinish || 0) + (item.actionSummary.CraftStop || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.CheatCommandItem || 0) + (item.actionSummary.CharacterAppearanceUpdate || 0)
|
||||||
|
+ (item.actionSummary.ItemTattooLevelUp || 0) + (item.actionSummary.UserCreate || 0) + (item.actionSummary.JoinInstance || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.totalDeltaCount)}</td>
|
||||||
|
</tr>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</TableStyle>
|
||||||
|
</TableWrapper>
|
||||||
|
<TopButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</AnimatedPageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ItemAcquireContent;
|
||||||
119
src/components/IndexManage/ItemAssetsContent.js
Normal file
119
src/components/IndexManage/ItemAssetsContent.js
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import React, { Fragment, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TableStyle,
|
||||||
|
FormWrapper,
|
||||||
|
TableWrapper, ListOption
|
||||||
|
} from '../../styles/Components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AssetsIndexSearchBar, useAssetsIndexSearch,
|
||||||
|
} from '../searchBar';
|
||||||
|
import { TopButton, ViewTableInfo } from '../common';
|
||||||
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
import { numberFormatter } from '../../utils';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
|
import CSVDownloadButton from '../common/button/CsvDownButton';
|
||||||
|
import DailyDashBoard from './DailyCaliumDashBoard';
|
||||||
|
|
||||||
|
const ItemAssetsContent = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
const tableRef = useRef(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
searchParams,
|
||||||
|
loading: dataLoading,
|
||||||
|
data: dataList,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
updateSearchParams
|
||||||
|
} = useAssetsIndexSearch(token);
|
||||||
|
|
||||||
|
const tableHeaders = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ id: 'logDay', label: '일자', width: '100px' },
|
||||||
|
{ id: 'userCount', label: '유저수', width: '100px' },
|
||||||
|
{ id: 'item_13080002', label: '퀘스트 메달', width: '100px' },
|
||||||
|
{ id: 'item_13080004', label: '보급품 메달', width: '100px' },
|
||||||
|
{ id: 'item_13080005', label: '제작 메달', width: '100px' },
|
||||||
|
{ id: 'item_13080006', label: '에테론 315 포드', width: '100px' },
|
||||||
|
{ id: 'item_13080007', label: '에테론 316 포드', width: '100px' },
|
||||||
|
{ id: 'item_13080008', label: '에테론 317 포드', width: '100px' },
|
||||||
|
{ id: 'item_13080009', label: '에테론 318 포드', width: '100px' },
|
||||||
|
{ id: 'item_11570001', label: '강화 잉크', width: '100px' },
|
||||||
|
{ id: 'item_11570002', label: '연성 잉크', width: '100px' }
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedPageWrapper>
|
||||||
|
<DailyDashBoard />
|
||||||
|
<FormWrapper>
|
||||||
|
<AssetsIndexSearchBar
|
||||||
|
searchParams={searchParams}
|
||||||
|
onSearch={(newParams, executeSearch = true) => {
|
||||||
|
if (executeSearch) {
|
||||||
|
handleSearch(newParams);
|
||||||
|
} else {
|
||||||
|
updateSearchParams(newParams);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReset={handleReset}
|
||||||
|
/>
|
||||||
|
</FormWrapper>
|
||||||
|
<ViewTableInfo >
|
||||||
|
<ListOption>
|
||||||
|
<CSVDownloadButton tableRef={tableRef} fileName={t('FILE_INDEX_ASSETS_ITEM')} />
|
||||||
|
</ListOption>
|
||||||
|
</ViewTableInfo>
|
||||||
|
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||||
|
<>
|
||||||
|
<TableWrapper>
|
||||||
|
<TableStyle ref={tableRef}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{tableHeaders.map(header => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
width={header.width}
|
||||||
|
colSpan={header.colSpan}
|
||||||
|
>
|
||||||
|
{header.label}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{dataList?.assets_list?.map((data, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
<tr>
|
||||||
|
<td>{data.logDay}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.userCount)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.item_13080002)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.item_13080004)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.item_13080005)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.item_13080006)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.item_13080007)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.item_13080008)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.item_13080009)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.item_11570001)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(data.item_11570002)}</td>
|
||||||
|
</tr>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</TableStyle>
|
||||||
|
</TableWrapper>
|
||||||
|
<TopButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</AnimatedPageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ItemAssetsContent;
|
||||||
143
src/components/IndexManage/ItemConsumeContent.js
Normal file
143
src/components/IndexManage/ItemConsumeContent.js
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import React, { Fragment, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TableStyle,
|
||||||
|
FormWrapper,
|
||||||
|
TableWrapper, ListOption, TableInfoContent, TextInput, Label, Notice,
|
||||||
|
} from '../../styles/Components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ItemAcquireIndexSearchBar,
|
||||||
|
ItemConsumeIndexSearchBar,
|
||||||
|
useItemAcquireIndexSearch,
|
||||||
|
useItemConsumeIndexSearch,
|
||||||
|
} from '../searchBar';
|
||||||
|
import { TopButton, ViewTableInfo } from '../common';
|
||||||
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
import { numberFormatter } from '../../utils';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
|
import CSVDownloadButton from '../common/button/CsvDownButton';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const ItemConsumeContent = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
const tableRef = useRef(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
searchParams,
|
||||||
|
loading: dataLoading,
|
||||||
|
data: dataList,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
updateSearchParams
|
||||||
|
} = useItemConsumeIndexSearch(token);
|
||||||
|
|
||||||
|
const tableHeaders = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ id: 'logDay', label: '일자', width: '100px' },
|
||||||
|
{ id: 'shopSell', label: '상점 판매', width: '80px' },
|
||||||
|
{ id: 'itemUse', label: '아이템 사용', width: '80px' },
|
||||||
|
{ id: 'beaconShop', label: '비컨상점', width: '80px' },
|
||||||
|
{ id: 'beacon', label: '비컨', width: '80px' },
|
||||||
|
{ id: 'quest', label: '퀘스트', width: '80px' },
|
||||||
|
{ id: 'ugq', label: 'UGQ', width: '80px' },
|
||||||
|
{ id: 'randomBox', label: '랜덤박스', width: '80px' },
|
||||||
|
{ id: 'myHome', label: '마이홈', width: '80px' },
|
||||||
|
{ id: 'craft', label: '크래프트', width: '80px' },
|
||||||
|
{ id: 'etc', label: '기타', width: '80px' },
|
||||||
|
{ id: 'summary', label: '합계', width: '80px' },
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedPageWrapper>
|
||||||
|
<FormWrapper>
|
||||||
|
<ItemConsumeIndexSearchBar
|
||||||
|
searchParams={searchParams}
|
||||||
|
onSearch={(newParams, executeSearch = true) => {
|
||||||
|
if (executeSearch) {
|
||||||
|
handleSearch(newParams);
|
||||||
|
} else {
|
||||||
|
updateSearchParams(newParams);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReset={handleReset}
|
||||||
|
/>
|
||||||
|
</FormWrapper>
|
||||||
|
<ViewTableInfo >
|
||||||
|
{dataList?.item_list && dataList.item_list.length > 0 &&
|
||||||
|
<TableInfoContent>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
value={dataList.item_list[0].itemId}
|
||||||
|
width="100px"
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
value={dataList.item_list[0].itemName}
|
||||||
|
width="300px"
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<Notice $color='#F15F5F'>* 확인되지 않은 액션이 있을 수 있습니다</Notice>
|
||||||
|
</TableInfoContent>
|
||||||
|
}
|
||||||
|
<ListOption>
|
||||||
|
<CSVDownloadButton tableRef={tableRef} fileName={t('FILE_INDEX_ITEM_CONSUME')} />
|
||||||
|
</ListOption>
|
||||||
|
</ViewTableInfo>
|
||||||
|
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||||
|
<>
|
||||||
|
<TableWrapper>
|
||||||
|
<TableStyle ref={tableRef}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{tableHeaders.map(header => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
width={header.width}
|
||||||
|
colSpan={header.colSpan}
|
||||||
|
>
|
||||||
|
{header.label}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{dataList?.item_list?.map((item, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
<tr>
|
||||||
|
<td>{item.logDay}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ShopSell)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ItemUse)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.BeaconShopRegisterItem || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.BeaconEdit || 0) + (item.actionSummary.BeaconCreate || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.QuestTaskUpdate || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.UgqAbort)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.actionSummary.ItemRandomBoxUse)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.SaveMyhome || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency((item.actionSummary.CraftStart || 0))}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(
|
||||||
|
(item.actionSummary.SummonParty || 0) + (item.actionSummary.ItemDestroy || 0) + (item.actionSummary.CreatePartyInstance || 0) + (item.actionSummary.ItemTattooChangeAttribute || 0)
|
||||||
|
+ (item.actionSummary.CheatCommandItem || 0) + (item.actionSummary.ItemDestoryByExpiration || 0) + (item.actionSummary.ItemDestroyByUser || 0) + (item.actionSummary.ItemTattooLevelUp || 0)
|
||||||
|
)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.totalDeltaCount)}</td>
|
||||||
|
</tr>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</TableStyle>
|
||||||
|
</TableWrapper>
|
||||||
|
<TopButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</AnimatedPageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ItemConsumeContent;
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
import { Fragment, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
|
||||||
|
|
||||||
import { TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
|
||||||
import { PlayTimeSearchBar } from '../../components/IndexManage/index';
|
|
||||||
import { PlaytimeIndexExport, PlaytimeIndexView } from '../../apis';
|
|
||||||
|
|
||||||
const PlayTimeContent = () => {
|
|
||||||
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();
|
|
||||||
|
|
||||||
const [dataList, setDataList] = useState([]);
|
|
||||||
const [resultData, setResultData] = useState([]);
|
|
||||||
|
|
||||||
const [sendDate, setSendDate] = useState(START_DATE);
|
|
||||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData(START_DATE, END_DATE);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 이용자 지표 데이터
|
|
||||||
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 PlaytimeIndexView(token, startDateToLocal, endDateToLocal));
|
|
||||||
|
|
||||||
setSendDate(startDateToLocal);
|
|
||||||
setFinishDate(endDateToLocal);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 검색 함수
|
|
||||||
const handleSearch = (send_dt, end_dt) => {
|
|
||||||
fetchData(send_dt, end_dt);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 엑셀 다운로드
|
|
||||||
const handleXlsxExport = () => {
|
|
||||||
const fileName = 'Caliverse_PlayTime_Index.xlsx';
|
|
||||||
PlaytimeIndexExport(token, fileName, sendDate, finishDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PlayTimeSearchBar setResultData={setResultData} resultData={resultData} handleSearch={handleSearch} fetchData={fetchData} />
|
|
||||||
<TableInfo>
|
|
||||||
<ListOption>
|
|
||||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
|
||||||
</ListOption>
|
|
||||||
</TableInfo>
|
|
||||||
<IndexTableWrap>
|
|
||||||
<TableStyle>
|
|
||||||
<caption></caption>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th rowSpan="2" width="140">
|
|
||||||
일자
|
|
||||||
</th>
|
|
||||||
<th colSpan="4" width="520">
|
|
||||||
유저수
|
|
||||||
</th>
|
|
||||||
<th rowSpan="2" width="160">
|
|
||||||
총 누적 플레이타임(분)
|
|
||||||
</th>
|
|
||||||
<th rowSpan="2" width="160">
|
|
||||||
1인당 평균 플레이타임(분)
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>30분 이내</th>
|
|
||||||
<th>30분 ~ 1시간</th>
|
|
||||||
<th>1시간 ~ 3시간</th>
|
|
||||||
<th>3시간 이상</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{dataList.playtime &&
|
|
||||||
dataList.playtime.map(time => (
|
|
||||||
<tr key={time.date}>
|
|
||||||
<td>{time.date}</td>
|
|
||||||
{time.user_cnt.map((cnt, index) => (
|
|
||||||
<td className="text-left" key={index}>
|
|
||||||
{cnt}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
<td className="text-left">{time.total_time}</td>
|
|
||||||
<td className="text-left">{time.average_time}</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</TableStyle>
|
|
||||||
</IndexTableWrap>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PlayTimeContent;
|
|
||||||
@@ -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 { useTranslation } from 'react-i18next';
|
||||||
|
import CSVDownloadButton from '../common/button/CsvDownButton';
|
||||||
|
|
||||||
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
|
<CSVDownloadButton tableRef={tableRef} fileName={t('FILE_INDEX_USER_RETENTION')} />
|
||||||
theme={excelBtn === true ? "disable" : "line"}
|
|
||||||
text="엑셀 다운로드"
|
|
||||||
disabled={handleXlsxExport}
|
|
||||||
handleClick={handleXlsxExport} />
|
|
||||||
</ListOption>
|
</ListOption>
|
||||||
</TableInfo>
|
</TableInfo>
|
||||||
<IndexTableWrap>
|
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||||
<TableStyle>
|
<IndexTableWrap>
|
||||||
<caption></caption>
|
<TableStyle ref={tableRef}>
|
||||||
<thead>
|
<caption></caption>
|
||||||
<tr>
|
<thead>
|
||||||
{/* <th width="100">국가</th> */}
|
<tr>
|
||||||
<th width="150">일자</th>
|
<th>일자</th>
|
||||||
<th className="cell-nru">NRU</th>
|
<th>NRU</th>
|
||||||
{[...Array(Number(retentionData))].map((value, index) => {
|
<th>D+1</th>
|
||||||
return <th key={index}>{`D+${index + 1}`}</th>;
|
<th>D+7</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>
|
||||||
</tr>
|
<td>{numberFormatter.formatPercent(data.d30_rate)}</td>
|
||||||
))}
|
</tr>
|
||||||
</tbody>
|
</Fragment>
|
||||||
</TableStyle>
|
))}
|
||||||
</IndexTableWrap>
|
</tbody>
|
||||||
</>
|
</TableStyle>
|
||||||
|
</IndexTableWrap>
|
||||||
|
}
|
||||||
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
import { Fragment, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
|
||||||
|
|
||||||
import { TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
|
||||||
import { SegmentSearchBar } from '../../components/IndexManage/index';
|
|
||||||
import { SegmentIndexExport, SegmentIndexView } from '../../apis';
|
|
||||||
|
|
||||||
const SegmentContent = () => {
|
|
||||||
const token = sessionStorage.getItem('token');
|
|
||||||
const END_DATE = new Date();
|
|
||||||
|
|
||||||
const [dataList, setDataList] = useState([]);
|
|
||||||
const [resultData, setResultData] = useState([]);
|
|
||||||
|
|
||||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData(END_DATE);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Retention 지표 데이터
|
|
||||||
const fetchData = async endDate => {
|
|
||||||
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 SegmentIndexView(token, endDateToLocal));
|
|
||||||
setFinishDate(endDateToLocal);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 검색 함수
|
|
||||||
const handleSearch = end_dt => {
|
|
||||||
fetchData(end_dt);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 엑셀 다운로드
|
|
||||||
const handleXlsxExport = () => {
|
|
||||||
const fileName = 'Caliverse_Segment_Index.xlsx';
|
|
||||||
SegmentIndexExport(token, fileName, finishDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SegmentSearchBar setResultData={setResultData} resultData={resultData} handleSearch={handleSearch} fetchData={fetchData} />
|
|
||||||
<TableInfo>
|
|
||||||
<ListOption>
|
|
||||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
|
||||||
</ListOption>
|
|
||||||
</TableInfo>
|
|
||||||
<IndexTableWrap>
|
|
||||||
<TableStyle>
|
|
||||||
<caption></caption>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colSpan="1" width="200">
|
|
||||||
{dataList && dataList.start_dt} ~ {dataList && dataList.end_dt}
|
|
||||||
</th>
|
|
||||||
<th colSpan="2" width="400">
|
|
||||||
KIP
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
{/* <th>국가</th> */}
|
|
||||||
<th>세그먼트 분류</th>
|
|
||||||
<th>AU</th>
|
|
||||||
<th>AU Percentage by User Segment (%)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{dataList && dataList.segment &&
|
|
||||||
dataList.segment.map((segment, index) => (
|
|
||||||
<tr key={index}>
|
|
||||||
{/* <td rowSpan="6">TH</td> */}
|
|
||||||
<td>{segment.type}</td>
|
|
||||||
<td>{segment.au}</td>
|
|
||||||
<td>{segment.dif}</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</TableStyle>
|
|
||||||
</IndexTableWrap>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SegmentContent;
|
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
import { TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
||||||
|
import { DailyDashBoard } from '../../components/IndexManage/index';
|
||||||
|
|
||||||
import { Title, TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
import { userIndexView } from '../../apis';
|
||||||
import { UserIndexSearchBar, DailyDashBoard } from '../../components/IndexManage/index';
|
|
||||||
|
|
||||||
import { userIndexView, userIndexExport } from '../../apis';
|
|
||||||
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';
|
||||||
|
import { UserIndexSearchBar } from '../searchBar';
|
||||||
|
|
||||||
const UserContent = () => {
|
const UserContent = () => {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
@@ -24,20 +23,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 +39,6 @@ const UserContent = () => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// setSendDate(startDateToLocal);
|
|
||||||
// setFinishDate(endDateToLocal);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 검색 함수
|
// 검색 함수
|
||||||
@@ -63,14 +46,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 +102,7 @@ const UserContent = () => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</TableStyle>
|
</TableStyle>
|
||||||
</IndexTableWrap>
|
</IndexTableWrap>
|
||||||
{loading && <Loading/>}
|
</AnimatedPageWrapper>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,219 +0,0 @@
|
|||||||
import { styled } from 'styled-components';
|
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
import { TableStyle, TableInfo, ListOption } from '../../styles/Components';
|
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
|
||||||
import VBPSearchBar from '../searchBar/VBPSearchBar';
|
|
||||||
import { VBPIndexExport, VbpIndexView } from '../../apis';
|
|
||||||
|
|
||||||
const VBPContent = () => {
|
|
||||||
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();
|
|
||||||
|
|
||||||
const [sendDate, setSendDate] = useState(START_DATE);
|
|
||||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
|
||||||
|
|
||||||
const [dataList, setDataList] = useState([]);
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData(START_DATE, END_DATE);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// console.log(dataList);
|
|
||||||
|
|
||||||
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 VbpIndexView(token, startDateToLocal, endDateToLocal));
|
|
||||||
|
|
||||||
setSendDate(startDateToLocal);
|
|
||||||
setFinishDate(endDateToLocal);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 엑셀 다운로드
|
|
||||||
const handleXlsxExport = () => {
|
|
||||||
const fileName = 'Caliverse_VBP_Index.xlsx';
|
|
||||||
VBPIndexExport(token, fileName, sendDate, finishDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<VBPSearchBar fetchData={fetchData} />
|
|
||||||
<TableInfo>
|
|
||||||
<ListOption>
|
|
||||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
|
||||||
</ListOption>
|
|
||||||
</TableInfo>
|
|
||||||
<TableWrapper>
|
|
||||||
<EconomicTable>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colSpan="2" className="text-center" width="300">
|
|
||||||
Product
|
|
||||||
</th>
|
|
||||||
<th width="160">2023-08-07</th>
|
|
||||||
<th width="160">2023-08-08</th>
|
|
||||||
<th width="160">2023-08-09</th>
|
|
||||||
<th width="160">2023-08-10</th>
|
|
||||||
<th width="160">2023-08-11</th>
|
|
||||||
<th width="160">2023-08-12</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<TableTitle colSpan="2">(Total) VBP 생산량</TableTitle>
|
|
||||||
<TableData>500000</TableData>
|
|
||||||
<TableData>500000</TableData>
|
|
||||||
<TableData>500000</TableData>
|
|
||||||
<TableData>500000</TableData>
|
|
||||||
<TableData>500000</TableData>
|
|
||||||
<TableData>500000</TableData>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<TableTitle colSpan="2">(Total) VBP 소진량</TableTitle>
|
|
||||||
<TableData>490000</TableData>
|
|
||||||
<TableData>490000</TableData>
|
|
||||||
<TableData>490000</TableData>
|
|
||||||
<TableData>490000</TableData>
|
|
||||||
<TableData>490000</TableData>
|
|
||||||
<TableData>490000</TableData>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<TableTitle colSpan="2">(Total) VBP 보유량</TableTitle>
|
|
||||||
<TableData>3.2M</TableData>
|
|
||||||
<TableData>3.3M</TableData>
|
|
||||||
<TableData>3.3M</TableData>
|
|
||||||
<TableData>3.4M</TableData>
|
|
||||||
<TableData>3.5M</TableData>
|
|
||||||
<TableData>3.5M</TableData>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<TableTitle rowSpan="2">GET</TableTitle>
|
|
||||||
<TableTitle>퀘스트 보상</TableTitle>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<TableTitle>시즌패스 보상</TableTitle>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<TableTitle rowSpan="2">USE</TableTitle>
|
|
||||||
<TableTitle>퀘스트 보상</TableTitle>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<TableTitle>시즌패스 보상</TableTitle>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
<TableData>150000</TableData>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{/* {mokupData.map((data, index) => (
|
|
||||||
<Fragment key={index}>
|
|
||||||
<tr>
|
|
||||||
<td>{data.date}</td>
|
|
||||||
<td>{data.name}</td>
|
|
||||||
<td>{data.trader}</td>
|
|
||||||
<td>{data.id}</td>
|
|
||||||
<td>{data.key}</td>
|
|
||||||
</tr>
|
|
||||||
</Fragment>
|
|
||||||
))} */}
|
|
||||||
</tbody>
|
|
||||||
</EconomicTable>
|
|
||||||
</TableWrapper>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default VBPContent;
|
|
||||||
|
|
||||||
const TableWrapper = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
min-width: 680px;
|
|
||||||
overflow: auto;
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
height: 4px;
|
|
||||||
}
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: #666666;
|
|
||||||
}
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background: #d9d9d9;
|
|
||||||
}
|
|
||||||
${TableStyle} {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 900px;
|
|
||||||
th {
|
|
||||||
&.cell-nru {
|
|
||||||
background: #f0f0f0;
|
|
||||||
border-left: 1px solid #aaa;
|
|
||||||
border-right: 1px solid #aaa;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
td {
|
|
||||||
&.blank {
|
|
||||||
background: #f9f9f9;
|
|
||||||
}
|
|
||||||
&.cell-nru {
|
|
||||||
background: #fafafa;
|
|
||||||
border-left: 1px solid #aaa;
|
|
||||||
border-right: 1px solid #aaa;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TableData = styled.td`
|
|
||||||
background: ${props => (props.$state === 'danger' ? '#d60000' : props.$state === 'blank' ? '#F9F9F9' : 'transparent')};
|
|
||||||
color: ${props => (props.$state === 'danger' ? '#fff' : '#2c2c2c')};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TableTitle = styled.td`
|
|
||||||
text-align: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const EconomicTable = styled(TableStyle)`
|
|
||||||
${TableData} {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
tbody {
|
|
||||||
tr:nth-child(1),
|
|
||||||
tr:nth-child(2),
|
|
||||||
tr:nth-child(3) {
|
|
||||||
background: #f5fcff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -1,27 +1,17 @@
|
|||||||
import UserIndexSearchBar from "../searchBar/UserIndexSearchBar";
|
|
||||||
import RetentionSearchBar from "../searchBar/RetentionSearchBar";
|
|
||||||
import SegmentSearchBar from "../searchBar/SegmentSearchBar";
|
|
||||||
import DailyDashBoard from "./DailyDashBoard";
|
import DailyDashBoard from "./DailyDashBoard";
|
||||||
import PlayTimeSearchBar from "../searchBar/PlayTimeSearchBar";
|
|
||||||
import UserContent from "./UserContent";
|
import UserContent from "./UserContent";
|
||||||
import PlayTimeContent from "./PlayTimeContent";
|
|
||||||
import RetentionContent from "./RetentionContent";
|
import RetentionContent from "./RetentionContent";
|
||||||
import SegmentContent from "./SegmentContent";
|
import CurrencyConsumeContent from "./CurrencyConsumeContent";
|
||||||
import DailyActiveUserContent from "./DailyActiveUserContent";
|
import CurrencyAcquireContent from "./CurrencyAcquireContent";
|
||||||
import DailyMedalContent from "./DailyMedalContent";
|
import ItemAcquireContent from "./ItemAcquireContent";
|
||||||
import DailySearchBar from "../searchBar/DailySearchBar";
|
import ItemConsumeContent from "./ItemConsumeContent";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
UserIndexSearchBar,
|
DailyDashBoard,
|
||||||
RetentionSearchBar,
|
|
||||||
SegmentSearchBar,
|
|
||||||
DailyDashBoard,
|
|
||||||
PlayTimeSearchBar,
|
|
||||||
UserContent,
|
UserContent,
|
||||||
SegmentContent,
|
|
||||||
RetentionContent,
|
RetentionContent,
|
||||||
PlayTimeContent,
|
CurrencyConsumeContent,
|
||||||
DailySearchBar,
|
CurrencyAcquireContent,
|
||||||
DailyActiveUserContent,
|
ItemAcquireContent,
|
||||||
DailyMedalContent,
|
ItemConsumeContent
|
||||||
};
|
};
|
||||||
@@ -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,
|
||||||
@@ -31,6 +32,18 @@ const ImageUploadBtn = ({ disabled,
|
|||||||
|
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
|
const koreanRegex = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/;
|
||||||
|
if (koreanRegex.test(file.name)) {
|
||||||
|
showToast('FILE_KOREAN_NAME_WARNING', {
|
||||||
|
type: alertTypes.warning
|
||||||
|
});
|
||||||
|
if (document.querySelector('#fileinput')) {
|
||||||
|
document.querySelector('#fileinput').value = '';
|
||||||
|
}
|
||||||
|
onFileDelete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 이미지 파일 확장자 체크
|
// 이미지 파일 확장자 체크
|
||||||
const fileExt = file.name.split('.').pop().toLowerCase();
|
const fileExt = file.name.split('.').pop().toLowerCase();
|
||||||
if (fileExt !== 'png' && fileExt !== 'jpg' && fileExt !== 'jpeg') {
|
if (fileExt !== 'png' && fileExt !== 'jpg' && fileExt !== 'jpeg') {
|
||||||
@@ -220,14 +233,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,79 +1,29 @@
|
|||||||
import React from 'react';
|
import { DatePicker } from 'antd';
|
||||||
import DatePickerComponent from './DatePickerComponent';
|
import dayjs from 'dayjs';
|
||||||
import { DatePickerWrapper } from '../../../styles/Components';
|
|
||||||
import {
|
const { RangePicker } = DatePicker;
|
||||||
FormRowGroup,
|
|
||||||
FormLabel,
|
|
||||||
DateContainer,
|
|
||||||
DateTimeWrapper,
|
|
||||||
DateTimeGroup,
|
|
||||||
} from '../../../styles/ModuleComponents';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const DateRangePicker = ({
|
const DateRangePicker = ({
|
||||||
label,
|
value,
|
||||||
startDate,
|
onChange,
|
||||||
endDate,
|
format,
|
||||||
onStartDateChange,
|
showTime = true,
|
||||||
onEndDateChange,
|
size = 'middle',
|
||||||
pastDate = new Date(),
|
...props
|
||||||
disabled,
|
|
||||||
startLabel = '시작 일자',
|
|
||||||
endLabel = '종료 일자',
|
|
||||||
setAlert,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const handleStartDate = (date) => {
|
|
||||||
const newDate = new Date(date);
|
|
||||||
onStartDateChange(newDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEndDate = (date) => {
|
|
||||||
let newDate = new Date(date);
|
|
||||||
|
|
||||||
if (startDate && newDate < startDate) {
|
|
||||||
setAlert(t('DATE_START_DIFF_END'));
|
|
||||||
newDate = new Date(startDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
onEndDateChange(newDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormRowGroup>
|
<RangePicker
|
||||||
<FormLabel>{label}</FormLabel>
|
showTime={showTime}
|
||||||
<DateTimeWrapper>
|
value={value ? [dayjs(value[0]), dayjs(value[1])] : [null, null]}
|
||||||
<DateTimeGroup>
|
format={format || 'YYYY-MM-DD HH:mm:ss'}
|
||||||
<DateContainer>
|
onChange={onChange}
|
||||||
<DatePickerWrapper>
|
placeholder={['시작 일시', '종료 일시']}
|
||||||
<DatePickerComponent
|
size={size}
|
||||||
name={startLabel}
|
allowClear={false}
|
||||||
handleSelectedDate={handleStartDate}
|
{...props}
|
||||||
selectedDate={startDate}
|
/>
|
||||||
pastDate={pastDate}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</DatePickerWrapper>
|
|
||||||
</DateContainer>
|
|
||||||
</DateTimeGroup>
|
|
||||||
|
|
||||||
<DateTimeGroup>
|
|
||||||
<DateContainer>
|
|
||||||
<DatePickerWrapper>
|
|
||||||
<DatePickerComponent
|
|
||||||
name={endLabel}
|
|
||||||
handleSelectedDate={handleEndDate}
|
|
||||||
selectedDate={endDate}
|
|
||||||
pastDate={pastDate}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</DatePickerWrapper>
|
|
||||||
</DateContainer>
|
|
||||||
</DateTimeGroup>
|
|
||||||
</DateTimeWrapper>
|
|
||||||
</FormRowGroup>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default DateRangePicker;
|
export default DateRangePicker;
|
||||||
@@ -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 {
|
const StyledMenu = styled(Menu)`
|
||||||
content: '';
|
`;
|
||||||
display: block;
|
|
||||||
width: 12px;
|
const MenuItemLink = styled(NavLink)`
|
||||||
height: 12px;
|
|
||||||
position: absolute;
|
|
||||||
right: 30px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(0, -50%);
|
|
||||||
background: url('${arrowIcon}') -12px 0 no-repeat;
|
|
||||||
}
|
|
||||||
&:hover,
|
|
||||||
&.active {
|
&.active {
|
||||||
background: #444;
|
font-weight: ${props => (props.$isclickable === 'false' ? 400 : 600)};
|
||||||
}
|
}
|
||||||
&.active ~ ul {
|
`;
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
&.active:before {
|
|
||||||
background: url('${arrowIcon}') 0 0 no-repeat;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SubMenu = styled.ul`
|
|
||||||
display: none;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SubMenuItem = styled.li`
|
|
||||||
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 {
|
|
||||||
color: ${props => (props.$isclickable === 'false' ? '#818181' : '#2c2c2c')};
|
|
||||||
font-weight: ${props => (props.$isclickable === 'false' ? 400 : 600)};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const BackGround = styled.div`
|
|
||||||
background: #eee2;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 100;
|
|
||||||
`;
|
|
||||||
205
src/components/common/Header/Navi_bak.js
Normal file
205
src/components/common/Header/Navi_bak.js
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import { NavLink, useNavigate } from 'react-router-dom';
|
||||||
|
import arrowIcon from '../../../assets/img/icon/icon-tab.png';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { authList } from '../../../store/authList';
|
||||||
|
import Modal from '../modal/Modal';
|
||||||
|
import { BtnWrapper, ButtonClose, ModalText } from '../../../styles/Components';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import Button from '../button/Button';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { AuthInfo } from '../../../apis';
|
||||||
|
import { getMenuConfig } from '../../../utils';
|
||||||
|
import { adminAuthLevel } from '../../../assets/data/types';
|
||||||
|
|
||||||
|
const Navi = () => {
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
const userInfo = useRecoilValue(authList);
|
||||||
|
const menu = getMenuConfig(userInfo);
|
||||||
|
|
||||||
|
const [modalClose, setModalClose] = useState('hidden');
|
||||||
|
const [logoutModalClose, setLogoutModalClose] = useState('hidden');
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleToken = async () => {
|
||||||
|
const tokenStatus = await AuthInfo(token);
|
||||||
|
|
||||||
|
tokenStatus.message === '잘못된 타입의 토큰입니다.' && setLogoutModalClose('view');
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleToken();
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
const handleTopMenu = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.target.classList.toggle('active');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLink = e => {
|
||||||
|
let topActive = document.querySelectorAll('nav .active');
|
||||||
|
let currentTopMenu = e.target.closest('ul').previousSibling;
|
||||||
|
for (let i = 0; i < topActive.length; i++) {
|
||||||
|
if (topActive[i] !== currentTopMenu) {
|
||||||
|
topActive[i].classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleToken();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 등록 완료 모달
|
||||||
|
const handleModalClose = () => {
|
||||||
|
if (modalClose === 'hidden') {
|
||||||
|
setModalClose('view');
|
||||||
|
} else {
|
||||||
|
setModalClose('hidden');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 로그아웃 안내 모달
|
||||||
|
const handleConfirmClose = () => {
|
||||||
|
if (logoutModalClose === 'hidden') {
|
||||||
|
setLogoutModalClose('view');
|
||||||
|
} else {
|
||||||
|
setLogoutModalClose('hidden');
|
||||||
|
sessionStorage.removeItem('token');
|
||||||
|
|
||||||
|
navigate('/');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isClickable = (submenu) => {
|
||||||
|
switch (userInfo.auth_level_type) {
|
||||||
|
case adminAuthLevel.DEVELOPER:
|
||||||
|
case adminAuthLevel.READER:
|
||||||
|
case adminAuthLevel.MASTER:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return submenu.authLevel === adminAuthLevel.NONE && userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === submenu.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
{menu.map((item, idx) => {
|
||||||
|
return (
|
||||||
|
<li key={idx}>
|
||||||
|
{item.access && (
|
||||||
|
<TopMenu to={item.link} onClick={handleTopMenu}>
|
||||||
|
{item.title}
|
||||||
|
</TopMenu>
|
||||||
|
)}
|
||||||
|
<SubMenu>
|
||||||
|
{item.submenu && userInfo &&
|
||||||
|
item.submenu.map((submenu, idx) => {
|
||||||
|
return (
|
||||||
|
<SubMenuItem key={idx} $isclickable={isClickable(submenu) ? 'true' : 'false'}>
|
||||||
|
<NavLink
|
||||||
|
to={isClickable(submenu) ? submenu.link : location.pathname}
|
||||||
|
onClick={e => {
|
||||||
|
isClickable(submenu) ? handleLink(e) : handleModalClose();
|
||||||
|
}}>
|
||||||
|
{submenu.title}
|
||||||
|
</NavLink>
|
||||||
|
</SubMenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SubMenu>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{/* 접근 불가 모달 */}
|
||||||
|
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={modalClose}>
|
||||||
|
<BtnWrapper $justify="flex-end">
|
||||||
|
<ButtonClose onClick={handleModalClose} />
|
||||||
|
</BtnWrapper>
|
||||||
|
<ModalText $align="center">
|
||||||
|
해당 메뉴에 대한 조회 권한이 없습니다.
|
||||||
|
<br />
|
||||||
|
권한 등급을 변경 후 다시 이용해주세요.
|
||||||
|
</ModalText>
|
||||||
|
<BtnWrapper $gap="10px">
|
||||||
|
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleModalClose} />
|
||||||
|
</BtnWrapper>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* 로그아웃 안내 모달 */}
|
||||||
|
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={logoutModalClose}>
|
||||||
|
<BtnWrapper $justify="flex-end">
|
||||||
|
<ButtonClose onClick={handleConfirmClose} />
|
||||||
|
</BtnWrapper>
|
||||||
|
<ModalText $align="center">로그아웃 되었습니다.</ModalText>
|
||||||
|
<BtnWrapper $gap="10px">
|
||||||
|
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleConfirmClose} />
|
||||||
|
</BtnWrapper>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Navi;
|
||||||
|
|
||||||
|
const TopMenu = styled(NavLink)`
|
||||||
|
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`
|
||||||
|
display: none;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SubMenuItem = styled.li`
|
||||||
|
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 {
|
||||||
|
color: ${props => (props.$isclickable === 'false' ? '#818181' : '#2c2c2c')};
|
||||||
|
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;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 64px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledBreadcrumb = styled(Breadcrumb)`
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ProfileContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 30px;
|
|
||||||
word-break: break-all;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const LogoutBtn = styled.button`
|
const StyledAvatar = styled(Avatar)`
|
||||||
color: #2c2c2c;
|
|
||||||
line-height: 1;
|
|
||||||
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 StyledUsername = styled(Text)`
|
||||||
padding-left: 35px;
|
font-weight: 600;
|
||||||
position: relative;
|
font-size: 18px;
|
||||||
font-size: 18px;
|
color: rgba(0, 0, 0, 0.85);
|
||||||
display: flex;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
background: url('${UserIcon}') 50% 50% no-repeat;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(0, -50%);
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Username = styled.div`
|
const StyledLogoutButton = styled(AntButton)`
|
||||||
font-weight: 700;
|
color: rgba(0, 0, 0, 0.45);
|
||||||
padding-right: 3px;
|
transition: color 0.3s;
|
||||||
`;
|
font-size: 18px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #1677ff;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
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,7 +2,9 @@ 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;
|
||||||
|
const { TextArea } = Input;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 위치 지정 가능한 그리드 형태 상세 정보 표시 컴포넌트
|
* 위치 지정 가능한 그리드 형태 상세 정보 표시 컴포넌트
|
||||||
@@ -50,9 +52,15 @@ const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 }
|
|||||||
handler,
|
handler,
|
||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
|
step,
|
||||||
format,
|
format,
|
||||||
required,
|
required,
|
||||||
showTime
|
showTime,
|
||||||
|
tabItems,
|
||||||
|
activeKey,
|
||||||
|
onTabChange,
|
||||||
|
maxLength,
|
||||||
|
rows: textareaRows
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
// 현재 값 가져오기 (formData에서 또는 항목에서)
|
// 현재 값 가져오기 (formData에서 또는 항목에서)
|
||||||
@@ -81,10 +89,35 @@ const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 }
|
|||||||
value={currentValue}
|
value={currentValue}
|
||||||
min={min}
|
min={min}
|
||||||
max={max}
|
max={max}
|
||||||
|
step={step || 1}
|
||||||
onChange={(value) => onChange(key, value, handler)}
|
onChange={(value) => onChange(key, value, handler)}
|
||||||
placeholder={placeholder || `${label} 입력`}
|
placeholder={placeholder || `${label} 입력`}
|
||||||
/>;
|
/>;
|
||||||
|
|
||||||
|
case 'display':
|
||||||
|
return <Input
|
||||||
|
{...commonProps}
|
||||||
|
value={currentValue || ''}
|
||||||
|
readOnly
|
||||||
|
style={{
|
||||||
|
...commonProps.style,
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
cursor: 'default'
|
||||||
|
}}
|
||||||
|
placeholder={placeholder || ''}
|
||||||
|
/>;
|
||||||
|
|
||||||
|
case 'textarea':
|
||||||
|
return <TextArea
|
||||||
|
{...commonProps}
|
||||||
|
value={currentValue || ''}
|
||||||
|
onChange={(e) => onChange(key, e.target.value, handler)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
maxLength={maxLength}
|
||||||
|
rows={textareaRows || 4}
|
||||||
|
showCount={!!maxLength}
|
||||||
|
/>;
|
||||||
|
|
||||||
case 'select':
|
case 'select':
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
@@ -92,10 +125,11 @@ const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 }
|
|||||||
value={currentValue}
|
value={currentValue}
|
||||||
onChange={(value) => onChange(key, value, handler)}
|
onChange={(value) => onChange(key, value, handler)}
|
||||||
placeholder={placeholder || `${label} 선택`}
|
placeholder={placeholder || `${label} 선택`}
|
||||||
|
popupMatchSelectWidth={false}
|
||||||
>
|
>
|
||||||
{options && options.map((option) => (
|
{options && options.map((option) => (
|
||||||
<Select.Option key={option.value} value={option.value}>
|
<Select.Option key={option.value} value={option.value}>
|
||||||
{option.label}
|
{option.name}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -105,6 +139,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)}
|
||||||
@@ -125,12 +161,25 @@ const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 }
|
|||||||
currentValue.end ? dayjs(currentValue.end) : null
|
currentValue.end ? dayjs(currentValue.end) : null
|
||||||
] : null)}
|
] : null)}
|
||||||
format={format || 'YYYY-MM-DD HH:mm:ss'}
|
format={format || 'YYYY-MM-DD HH:mm:ss'}
|
||||||
onChange={(dates, dateStrings) => {
|
onChange={(dates) => {
|
||||||
if (dates) {
|
if (dates && dates.length === 2) {
|
||||||
// 두 개의 별도 필드에 각각 업데이트
|
// 두 개의 별도 필드에 각각 업데이트
|
||||||
if (item.keys) {
|
if (item.keys) {
|
||||||
onChange(item.keys.start, dates[0], handler);
|
// 두 개의 onChange를 순차적으로 호출하는 대신
|
||||||
onChange(item.keys.end, dates[1], handler);
|
// 한 번에 두 필드를 모두 업데이트하는 방식으로 변경
|
||||||
|
const updatedData = {
|
||||||
|
...formData,
|
||||||
|
[item.keys.start]: dates[0],
|
||||||
|
[item.keys.end]: dates[1]
|
||||||
|
};
|
||||||
|
|
||||||
|
// handler가 있으면 handler 실행, 없으면 직접 onChange 호출
|
||||||
|
if (handler) {
|
||||||
|
handler(dates, key, updatedData);
|
||||||
|
} else {
|
||||||
|
// onChange를 통해 전체 업데이트된 데이터를 전달
|
||||||
|
onChange('dateRange_update', updatedData, null);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 기존 방식 지원 (하위 호환성)
|
// 기존 방식 지원 (하위 호환성)
|
||||||
onChange(key, {
|
onChange(key, {
|
||||||
@@ -141,8 +190,17 @@ const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 }
|
|||||||
} else {
|
} else {
|
||||||
// 두 필드 모두 비우기
|
// 두 필드 모두 비우기
|
||||||
if (item.keys) {
|
if (item.keys) {
|
||||||
onChange(item.keys.start, null, handler);
|
const updatedData = {
|
||||||
onChange(item.keys.end, null, handler);
|
...formData,
|
||||||
|
[item.keys.start]: null,
|
||||||
|
[item.keys.end]: null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (handler) {
|
||||||
|
handler(null, key, updatedData);
|
||||||
|
} else {
|
||||||
|
onChange('dateRange_update', updatedData, null);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
onChange(key, { start: null, end: null }, handler);
|
onChange(key, { start: null, end: null }, handler);
|
||||||
}
|
}
|
||||||
@@ -191,15 +249,24 @@ 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':
|
||||||
return item.render ? item.render(formData, onChange) : null;
|
return item.render ? item.render(formData, onChange) : null;
|
||||||
|
|
||||||
|
case 'label':
|
||||||
default:
|
default:
|
||||||
return <div>{currentValue}</div>;
|
return <div style={{
|
||||||
|
padding: '4px 11px',
|
||||||
|
minHeight: '32px',
|
||||||
|
lineHeight: '24px',
|
||||||
|
fontSize: '15px',
|
||||||
|
color: currentValue ? '#000' : '#bfbfbf'
|
||||||
|
}}>
|
||||||
|
{currentValue || placeholder || ''}
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -249,13 +316,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 +340,7 @@ const StatusDisplay = ({ status }) => {
|
|||||||
text = '삭제';
|
text = '삭제';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
color = '#1890ff';
|
color = '#DEBB46';
|
||||||
text = status;
|
text = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
55
src/components/common/Layout/DetailInfo.js
Normal file
55
src/components/common/Layout/DetailInfo.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Card, Descriptions } from 'antd';
|
||||||
|
import { getFieldLabel } from '../../../utils';
|
||||||
|
|
||||||
|
const InfoCard = ({
|
||||||
|
title,
|
||||||
|
data,
|
||||||
|
keyPrefix = 'item',
|
||||||
|
size = 'small',
|
||||||
|
column = 1,
|
||||||
|
bordered = true,
|
||||||
|
type = 'inner'
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
if (!data ||
|
||||||
|
typeof data !== 'object' ||
|
||||||
|
Object.keys(data).length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = Object.entries(data).map(([key, value]) => ({
|
||||||
|
key: `${keyPrefix}-${key}`,
|
||||||
|
label: getFieldLabel(key),
|
||||||
|
children: (() => {
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.join(', ');
|
||||||
|
}
|
||||||
|
return JSON.stringify(value, null, 2);
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
})()
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
size={size}
|
||||||
|
title={title}
|
||||||
|
type={type}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
>
|
||||||
|
<Descriptions
|
||||||
|
bordered={bordered}
|
||||||
|
column={column}
|
||||||
|
size={size}
|
||||||
|
items={items}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InfoCard;
|
||||||
@@ -22,24 +22,36 @@ const DetailLayout = ({
|
|||||||
}) => {
|
}) => {
|
||||||
// 값 변경 핸들러
|
// 값 변경 핸들러
|
||||||
const handleChange = (key, value, handler) => {
|
const handleChange = (key, value, handler) => {
|
||||||
// 핸들러가 있으면 핸들러 실행
|
let updatedFormData = { ...formData };
|
||||||
if (handler) {
|
|
||||||
handler(value, key, formData);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// dateRange 전용 업데이트 처리
|
||||||
|
if (key === 'dateRange_update') {
|
||||||
|
updatedFormData = value; // value가 이미 완전히 업데이트된 객체
|
||||||
|
}
|
||||||
// 키가 점 표기법이면 중첩 객체 업데이트
|
// 키가 점 표기법이면 중첩 객체 업데이트
|
||||||
if (key.includes('.')) {
|
else if (key.includes('.')) {
|
||||||
const [parentKey, childKey] = key.split('.');
|
const [parentKey, childKey] = key.split('.');
|
||||||
onChange({
|
updatedFormData = {
|
||||||
...formData,
|
...formData,
|
||||||
[parentKey]: {
|
[parentKey]: {
|
||||||
...formData[parentKey],
|
...formData[parentKey],
|
||||||
[childKey]: value
|
[childKey]: value
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
} else {
|
} else {
|
||||||
// 일반 키는 직접 업데이트
|
// 일반 키는 직접 업데이트
|
||||||
onChange({ ...formData, [key]: value });
|
updatedFormData = {
|
||||||
|
...formData,
|
||||||
|
[key]: value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 핸들러가 있으면 핸들러 실행 (업데이트된 데이터를 전달)
|
||||||
|
if (handler) {
|
||||||
|
handler(value, key, updatedFormData);
|
||||||
|
} else {
|
||||||
|
// 핸들러가 없으면 직접 onChange 호출
|
||||||
|
onChange(updatedFormData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
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';
|
||||||
|
import InfoCard from './DetailInfo'
|
||||||
|
|
||||||
export { Layout, LoginLayout, MainLayout };
|
export { Layout, LoginLayout, MainLayout, AnimatedPageWrapper, DetailGrid, DetailLayout, InfoCard };
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const SearchBarLayout = ({ firstColumnData, secondColumnData, filter, direction,
|
|||||||
</SearchRow>
|
</SearchRow>
|
||||||
)}
|
)}
|
||||||
{isSearch &&
|
{isSearch &&
|
||||||
<SearchRow>
|
<SearchRow direction={direction}>
|
||||||
<BtnWrapper $gap="8px">
|
<BtnWrapper $gap="8px">
|
||||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="button" />
|
<Button theme="search" text="검색" handleClick={handleSubmit} type="button" />
|
||||||
<Button theme="reset" handleClick={onReset} type="button" />
|
<Button theme="reset" handleClick={onReset} type="button" />
|
||||||
|
|||||||
390
src/components/common/button/CsvDownButton.js
Normal file
390
src/components/common/button/CsvDownButton.js
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
import { ExcelDownButton } from '../../../styles/ModuleComponents';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const CSVDownloadButton = ({ tableRef, data, fileName = 'download.csv', onLoadingChange }) => {
|
||||||
|
const [isDownloading, setIsDownloading] = useState(false);
|
||||||
|
const [lastProgress, setLastProgress] = useState(0);
|
||||||
|
|
||||||
|
// 타임아웃 감지 및 처리
|
||||||
|
useEffect(() => {
|
||||||
|
let timeoutTimer;
|
||||||
|
|
||||||
|
if (isDownloading && lastProgress >= 95) {
|
||||||
|
// 최종 단계에서 타임아웃 감지 타이머 설정
|
||||||
|
timeoutTimer = setTimeout(() => {
|
||||||
|
// 진행 상태가 여전히 변하지 않았다면 타임아웃으로 간주
|
||||||
|
if (isDownloading && lastProgress >= 95) {
|
||||||
|
console.log("CSV download timeout detected, completing process");
|
||||||
|
setIsDownloading(false);
|
||||||
|
if (onLoadingChange) {
|
||||||
|
onLoadingChange({ loading: false, progress: 100 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 10000); // 10초 타임아웃 (CSV는 Excel보다 빠르므로 시간 단축)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
||||||
|
};
|
||||||
|
}, [isDownloading, lastProgress, onLoadingChange]);
|
||||||
|
|
||||||
|
const flattenObject = (obj, prefix = '') => {
|
||||||
|
return Object.keys(obj).reduce((acc, key) => {
|
||||||
|
const prefixedKey = prefix ? `${prefix}.${key}` : key;
|
||||||
|
|
||||||
|
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
||||||
|
Object.assign(acc, flattenObject(obj[key], prefixedKey));
|
||||||
|
} else if (Array.isArray(obj[key])) {
|
||||||
|
// 배열은 JSON 문자열로 변환
|
||||||
|
acc[prefixedKey] = JSON.stringify(obj[key]);
|
||||||
|
} else {
|
||||||
|
acc[prefixedKey] = obj[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateLoadingState = (newProgress) => {
|
||||||
|
setLastProgress(newProgress);
|
||||||
|
if (onLoadingChange && typeof onLoadingChange === 'function') {
|
||||||
|
onLoadingChange({loading: true, progress: newProgress});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// CSV 문자열 이스케이프 처리
|
||||||
|
const escapeCSVField = (field) => {
|
||||||
|
if (field === null || field === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const str = String(field);
|
||||||
|
|
||||||
|
// 쉼표, 따옴표, 줄바꿈이 있는 경우 따옴표로 감싸고 내부 따옴표는 두 개로 변환
|
||||||
|
if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r')) {
|
||||||
|
return '"' + str.replace(/"/g, '""') + '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 배열을 CSV 행으로 변환
|
||||||
|
const arrayToCSVRow = (array) => {
|
||||||
|
return array.map(escapeCSVField).join(',');
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadTableCSV = async () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if (!tableRef || !tableRef.current) {
|
||||||
|
reject(new Error('테이블 참조가 없습니다.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLoadingState(10);
|
||||||
|
|
||||||
|
// 메인 스레드에서 데이터 추출
|
||||||
|
const tableElement = tableRef.current;
|
||||||
|
const headerRows = tableElement.getElementsByTagName('thead')[0].getElementsByTagName('tr');
|
||||||
|
const bodyRows = tableElement.getElementsByTagName('tbody')[0].getElementsByTagName('tr');
|
||||||
|
|
||||||
|
// 일반 행만 포함 (상세 행 제외)
|
||||||
|
const normalBodyRows = Array.from(bodyRows).filter(row => {
|
||||||
|
const hasTdWithColspan = Array.from(row.cells).some(cell => cell.hasAttribute('colspan'));
|
||||||
|
return !hasTdWithColspan;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 헤더 데이터 추출
|
||||||
|
const headers = Array.from(headerRows[0].cells).map(cell => cell.textContent);
|
||||||
|
|
||||||
|
updateLoadingState(30);
|
||||||
|
|
||||||
|
// 바디 데이터 추출
|
||||||
|
const bodyData = normalBodyRows.map(row =>
|
||||||
|
Array.from(row.cells).map(cell => cell.textContent)
|
||||||
|
);
|
||||||
|
|
||||||
|
updateLoadingState(50);
|
||||||
|
|
||||||
|
// CSV 생성을 비동기로 처리
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
// CSV 문자열 생성
|
||||||
|
let csvContent = '';
|
||||||
|
|
||||||
|
// 헤더 추가
|
||||||
|
csvContent += arrayToCSVRow(headers) + '\n';
|
||||||
|
|
||||||
|
updateLoadingState(70);
|
||||||
|
|
||||||
|
// 데이터 행들을 청크로 처리
|
||||||
|
const chunkSize = 1000;
|
||||||
|
let currentIndex = 0;
|
||||||
|
|
||||||
|
function processDataChunk() {
|
||||||
|
const end = Math.min(currentIndex + chunkSize, bodyData.length);
|
||||||
|
|
||||||
|
for (let i = currentIndex; i < end; i++) {
|
||||||
|
csvContent += arrayToCSVRow(bodyData[i]) + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIndex = end;
|
||||||
|
const progress = 70 + Math.floor((currentIndex / bodyData.length) * 20);
|
||||||
|
updateLoadingState(progress);
|
||||||
|
|
||||||
|
if (currentIndex < bodyData.length) {
|
||||||
|
// 아직 처리할 데이터가 남아있으면 다음 청크 처리 예약
|
||||||
|
setTimeout(processDataChunk, 0);
|
||||||
|
} else {
|
||||||
|
// 모든 데이터 처리 완료 후 다운로드
|
||||||
|
finishCSVDownload(csvContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishCSVDownload(csvContent) {
|
||||||
|
try {
|
||||||
|
updateLoadingState(95);
|
||||||
|
|
||||||
|
// BOM 추가 (한글 깨짐 방지)
|
||||||
|
const BOM = '\uFEFF';
|
||||||
|
const csvWithBOM = BOM + csvContent;
|
||||||
|
|
||||||
|
// Blob 생성 및 다운로드
|
||||||
|
const blob = new Blob([csvWithBOM], { type: 'text/csv;charset=utf-8;' });
|
||||||
|
const link = document.createElement('a');
|
||||||
|
|
||||||
|
if (link.download !== undefined) {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
link.setAttribute('href', url);
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
link.style.visibility = 'hidden';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLoadingState(100);
|
||||||
|
resolve();
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 첫 번째 청크 처리 시작
|
||||||
|
processDataChunk();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const chunkArray = (array, chunkSize) => {
|
||||||
|
const chunks = [];
|
||||||
|
for (let i = 0; i < array.length; i += chunkSize) {
|
||||||
|
chunks.push(array.slice(i, i + chunkSize));
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadDataCSV = async () => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
reject(new Error('다운로드할 데이터가 없습니다.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLoadingState(5);
|
||||||
|
|
||||||
|
// 데이터 플랫 변환 과정을 더 작은 청크로 나누기
|
||||||
|
const dataChunkSize = 2000;
|
||||||
|
const dataChunks = chunkArray(data, dataChunkSize);
|
||||||
|
let flattenedData = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < dataChunks.length; i++) {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// 청크 내 아이템들을 플랫하게 변환
|
||||||
|
const chunkData = dataChunks[i].map(item => {
|
||||||
|
// 기본 필드
|
||||||
|
const baseData = {
|
||||||
|
'logTime': item.logTime,
|
||||||
|
'GUID': item.userGuid === 'None' ? '' : item.userGuid,
|
||||||
|
'Nickname': item.userNickname === 'None' ? '' : item.userNickname,
|
||||||
|
'Account ID': item.accountId === 'None' ? '' : item.accountId,
|
||||||
|
'Action': item.action,
|
||||||
|
'Domain': item.domain === 'None' ? '' : item.domain,
|
||||||
|
'Tran ID': item.tranId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Actor 데이터 플랫하게 추가
|
||||||
|
const actorData = item.header && item.header.Actor ?
|
||||||
|
flattenObject(item.header.Actor, 'Actor') : {};
|
||||||
|
|
||||||
|
// Infos 데이터 플랫하게 추가
|
||||||
|
let infosData = {};
|
||||||
|
if (item.body && item.body.Infos && Array.isArray(item.body.Infos)) {
|
||||||
|
item.body.Infos.forEach((info) => {
|
||||||
|
infosData = {
|
||||||
|
...infosData,
|
||||||
|
...flattenObject(info, `Info`)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseData,
|
||||||
|
...actorData,
|
||||||
|
...infosData
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
flattenedData = [...flattenedData, ...chunkData];
|
||||||
|
const progress = 5 + Math.floor((i + 1) / dataChunks.length * 10);
|
||||||
|
updateLoadingState(progress);
|
||||||
|
resolve();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 모든 항목의 모든 키 수집하여 헤더 생성
|
||||||
|
const allKeys = new Set();
|
||||||
|
|
||||||
|
// 헤더 수집도 청크로 나누기
|
||||||
|
for (let i = 0; i < flattenedData.length; i += dataChunkSize) {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const end = Math.min(i + dataChunkSize, flattenedData.length);
|
||||||
|
for (let j = i; j < end; j++) {
|
||||||
|
Object.keys(flattenedData[j]).forEach(key => allKeys.add(key));
|
||||||
|
}
|
||||||
|
const progress = 15 + Math.floor((i + dataChunkSize) / flattenedData.length * 5);
|
||||||
|
updateLoadingState(progress);
|
||||||
|
resolve();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = Array.from(allKeys);
|
||||||
|
updateLoadingState(25);
|
||||||
|
|
||||||
|
// CSV 생성
|
||||||
|
let csvContent = '';
|
||||||
|
|
||||||
|
// 헤더 추가
|
||||||
|
csvContent += arrayToCSVRow(headers) + '\n';
|
||||||
|
updateLoadingState(30);
|
||||||
|
|
||||||
|
// 청크로 데이터 나누기
|
||||||
|
const chunkSize = 1000;
|
||||||
|
const rowChunks = chunkArray(flattenedData, chunkSize);
|
||||||
|
|
||||||
|
// 각 청크 처리
|
||||||
|
let processedCount = 0;
|
||||||
|
for (const chunk of rowChunks) {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// 각 청크의 데이터를 CSV 행으로 변환
|
||||||
|
chunk.forEach(item => {
|
||||||
|
const row = headers.map(header => {
|
||||||
|
const value = item[header] !== undefined ? item[header] : '';
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
csvContent += arrayToCSVRow(row) + '\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
processedCount += chunk.length;
|
||||||
|
|
||||||
|
// 진행률 계산 및 콜백 호출
|
||||||
|
const newProgress = Math.min(90, Math.round((processedCount / flattenedData.length) * 60) + 30);
|
||||||
|
updateLoadingState(newProgress);
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 메모리 정리를 위한 가비지 컬렉션 힌트
|
||||||
|
if (processedCount % (chunkSize * 5) === 0) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLoadingState(95);
|
||||||
|
|
||||||
|
// 파일 다운로드
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
// BOM 추가 (한글 깨짐 방지)
|
||||||
|
const BOM = '\uFEFF';
|
||||||
|
const csvWithBOM = BOM + csvContent;
|
||||||
|
|
||||||
|
// Blob 생성 및 다운로드
|
||||||
|
const blob = new Blob([csvWithBOM], { type: 'text/csv;charset=utf-8;' });
|
||||||
|
const link = document.createElement('a');
|
||||||
|
|
||||||
|
if (link.download !== undefined) {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
link.setAttribute('href', url);
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
link.style.visibility = 'hidden';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLoadingState(100);
|
||||||
|
resolve();
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownload = useCallback(async () => {
|
||||||
|
if (isDownloading) return; // 이미 다운로드 중이면 중복 실행 방지
|
||||||
|
|
||||||
|
setIsDownloading(true);
|
||||||
|
setLastProgress(0);
|
||||||
|
if (onLoadingChange) onLoadingChange({loading: true, progress: 0});
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (tableRef) {
|
||||||
|
await downloadTableCSV();
|
||||||
|
} else if (data) {
|
||||||
|
await downloadDataCSV();
|
||||||
|
} else {
|
||||||
|
alert('유효한 데이터 소스가 없습니다.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('CSV download failed:', error);
|
||||||
|
alert('CSV 다운로드 중 오류가 발생했습니다.');
|
||||||
|
} finally {
|
||||||
|
// 다운로드 완료 후 짧은 지연 시간을 두어 100% 상태를 잠시 보여줌
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsDownloading(false);
|
||||||
|
if (onLoadingChange) onLoadingChange({loading: false, progress: 100});
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}, [tableRef, data, fileName, isDownloading, onLoadingChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExcelDownButton onClick={handleDownload} disabled={isDownloading}>
|
||||||
|
{isDownloading ? '다운로드 중...' : '엑셀 다운로드'}
|
||||||
|
</ExcelDownButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CSVDownloadButton;
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import * as XLSX from 'xlsx-js-style';
|
|
||||||
import { ExcelDownButton } from '../../../styles/ModuleComponents';
|
|
||||||
|
|
||||||
const ExcelDownloadButton = ({ tableRef, fileName = 'download.xlsx', sheetName = 'Sheet1' }) => {
|
|
||||||
const isNumeric = (value) => {
|
|
||||||
// 숫자 또는 숫자 문자열인지 확인
|
|
||||||
return !isNaN(value) && !isNaN(parseFloat(value));
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadExcel = () => {
|
|
||||||
try {
|
|
||||||
if (!tableRef.current) return;
|
|
||||||
|
|
||||||
const tableElement = tableRef.current;
|
|
||||||
const headerRows = tableElement.getElementsByTagName('thead')[0].getElementsByTagName('tr');
|
|
||||||
const bodyRows = tableElement.getElementsByTagName('tbody')[0].getElementsByTagName('tr');
|
|
||||||
|
|
||||||
// 헤더 데이터 추출
|
|
||||||
const headers = Array.from(headerRows[0].cells).map(cell => cell.textContent);
|
|
||||||
|
|
||||||
// 바디 데이터 추출 및 숫자 타입 처리
|
|
||||||
const bodyData = Array.from(bodyRows).map(row =>
|
|
||||||
Array.from(row.cells).map(cell => {
|
|
||||||
const value = cell.textContent;
|
|
||||||
return isNumeric(value) ? parseFloat(value) : value;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// 워크북 생성
|
|
||||||
const wb = XLSX.utils.book_new();
|
|
||||||
|
|
||||||
// 테두리 스타일 정의
|
|
||||||
const borderStyle = {
|
|
||||||
style: "thin",
|
|
||||||
color: { rgb: "000000" }
|
|
||||||
};
|
|
||||||
|
|
||||||
// 스타일 정의
|
|
||||||
const centerStyle = {
|
|
||||||
font: {
|
|
||||||
name: "맑은 고딕",
|
|
||||||
sz: 11
|
|
||||||
},
|
|
||||||
alignment: {
|
|
||||||
horizontal: 'right',
|
|
||||||
vertical: 'right'
|
|
||||||
},
|
|
||||||
border: {
|
|
||||||
top: borderStyle,
|
|
||||||
bottom: borderStyle,
|
|
||||||
left: borderStyle,
|
|
||||||
right: borderStyle
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const headerStyle = {
|
|
||||||
alignment: {
|
|
||||||
horizontal: 'center',
|
|
||||||
vertical: 'center'
|
|
||||||
},
|
|
||||||
fill: {
|
|
||||||
fgColor: { rgb: "d9e1f2" },
|
|
||||||
patternType: "solid"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 데이터에 스타일 적용
|
|
||||||
const wsData = [
|
|
||||||
// 헤더 행
|
|
||||||
headers.map(h => ({
|
|
||||||
v: h,
|
|
||||||
s: headerStyle
|
|
||||||
})),
|
|
||||||
// 데이터 행들
|
|
||||||
...bodyData.map(row =>
|
|
||||||
row.map(cell => ({
|
|
||||||
v: cell,
|
|
||||||
s: centerStyle
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
// 워크시트 생성
|
|
||||||
const ws = XLSX.utils.aoa_to_sheet(wsData);
|
|
||||||
|
|
||||||
// 열 너비 설정 (최소 8, 최대 50)
|
|
||||||
ws['!cols'] = headers.map((_, index) => {
|
|
||||||
const maxLength = Math.max(
|
|
||||||
headers[index].length * 2,
|
|
||||||
...bodyData.map(row => String(row[index] || '').length * 1.2)
|
|
||||||
);
|
|
||||||
return { wch: Math.max(8, Math.min(50, maxLength)) };
|
|
||||||
});
|
|
||||||
|
|
||||||
// 워크시트를 워크북에 추가
|
|
||||||
XLSX.utils.book_append_sheet(wb, ws, sheetName);
|
|
||||||
|
|
||||||
// 엑셀 파일 다운로드
|
|
||||||
XLSX.writeFile(wb, fileName);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Excel download failed:', error);
|
|
||||||
alert('엑셀 다운로드 중 오류가 발생했습니다.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ExcelDownButton onClick={downloadExcel}>
|
|
||||||
엑셀 다운로드
|
|
||||||
</ExcelDownButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ExcelDownloadButton;
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const DotsButton = styled.button`
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
border: none;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 점 스타일링 */
|
|
||||||
.dot {
|
|
||||||
width: 3px;
|
|
||||||
height: 3px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #333;
|
|
||||||
margin: 2px 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const VerticalDotsButton = ({ text, type = 'button', errorMessage, handleClick, size, width, height, borderColor, disabled, name }) => {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DotsButton
|
|
||||||
onSubmit={e => e.preventDefault()}
|
|
||||||
type={type}
|
|
||||||
disabled={disabled}
|
|
||||||
onClick={handleClick}
|
|
||||||
size={size}
|
|
||||||
bordercolor={borderColor}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
name={name}
|
|
||||||
>
|
|
||||||
<div className="dot"></div>
|
|
||||||
<div className="dot"></div>
|
|
||||||
<div className="dot"></div>
|
|
||||||
</DotsButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default VerticalDotsButton;
|
|
||||||
@@ -1,57 +1,123 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled, { keyframes } from 'styled-components';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
|
||||||
// 통합된 애니메이션 탭 컴포넌트
|
// 통합된 애니메이션 탭 컴포넌트
|
||||||
const AnimatedTabs = ({ items, activeKey, onChange }) => {
|
const AnimatedTabs = ({ items, activeKey, onChange, tabPosition = 'center' }) => {
|
||||||
|
// 각 항목의 children을 애니메이션 래퍼로 감싸기
|
||||||
|
const tabItems = items.map(item => ({
|
||||||
|
key: item.key,
|
||||||
|
label: item.label,
|
||||||
|
children: (
|
||||||
|
<AnimatedContent key={`content-${item.key}`}>
|
||||||
|
{item.children}
|
||||||
|
</AnimatedContent>
|
||||||
|
)
|
||||||
|
|
||||||
|
// children: (
|
||||||
|
// <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>
|
||||||
|
// )
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTabs
|
<StyledTabs
|
||||||
|
type="card"
|
||||||
activeKey={activeKey}
|
activeKey={activeKey}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
centered={true}
|
centered={tabPosition === 'center'}
|
||||||
>
|
items={tabItems}
|
||||||
{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 slideInRight = keyframes`
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fadeIn = keyframes`
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
const AnimatedContent = styled.div`
|
||||||
|
animation: ${slideInRight} 0.3s ease-out;
|
||||||
|
|
||||||
|
/* 대안으로 더 부드러운 페이드 인 효과 */
|
||||||
|
/* animation: ${fadeIn} 0.4s ease-out; */
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
// 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%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
//align-items: center;
|
||||||
|
|
||||||
.ant-tabs-nav {
|
//.ant-tabs-nav {
|
||||||
margin-bottom: 16px;
|
// margin-bottom: 16px;
|
||||||
width: 80%;
|
// width: 80%;
|
||||||
}
|
//}
|
||||||
|
|
||||||
.ant-tabs-nav-wrap {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-tabs-tab {
|
.ant-tabs-tab {
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const AIMessageInput = ({ onSendMessage }) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [message, setMessage] = useState('');
|
|
||||||
const [isSending, setIsSending] = useState(false);
|
|
||||||
const textareaRef = useRef(null);
|
|
||||||
const modalRef = useRef(null);
|
|
||||||
|
|
||||||
// 텍스트 영역 높이 자동 조절
|
|
||||||
useEffect(() => {
|
|
||||||
if (textareaRef.current && isOpen) {
|
|
||||||
textareaRef.current.style.height = 'auto';
|
|
||||||
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`;
|
|
||||||
}
|
|
||||||
}, [message, isOpen]);
|
|
||||||
|
|
||||||
// 모달 외부 클릭시 닫기
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (event) => {
|
|
||||||
if (modalRef.current && !modalRef.current.contains(event.target)) {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isOpen) {
|
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('mousedown', handleClickOutside);
|
|
||||||
};
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
// 모달 열기
|
|
||||||
const openModal = () => {
|
|
||||||
setIsOpen(true);
|
|
||||||
// 모달이 열린 후 텍스트 영역에 포커스
|
|
||||||
setTimeout(() => {
|
|
||||||
if (textareaRef.current) {
|
|
||||||
textareaRef.current.focus();
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 모달 닫기
|
|
||||||
const closeModal = () => {
|
|
||||||
setIsOpen(false);
|
|
||||||
setMessage('');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 메시지 전송 처리
|
|
||||||
const handleSendMessage = () => {
|
|
||||||
if (message.trim() && !isSending) {
|
|
||||||
setIsSending(true);
|
|
||||||
|
|
||||||
// 메시지 전송 처리
|
|
||||||
if (onSendMessage) {
|
|
||||||
onSendMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 입력 초기화 및 상태 업데이트
|
|
||||||
setMessage('');
|
|
||||||
setIsSending(false);
|
|
||||||
|
|
||||||
// 모달 닫기
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 엔터 키 처리 (Shift+Enter 줄바꿈, Enter 전송)
|
|
||||||
const handleKeyDown = (e) => {
|
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
|
||||||
e.preventDefault();
|
|
||||||
handleSendMessage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* 메뉴 버튼 */}
|
|
||||||
<MenuButton onClick={openModal}>
|
|
||||||
<div className="dot"></div>
|
|
||||||
<div className="dot"></div>
|
|
||||||
<div className="dot"></div>
|
|
||||||
</MenuButton>
|
|
||||||
|
|
||||||
{/* 모달 오버레이 */}
|
|
||||||
<ModalOverlay isOpen={isOpen}>
|
|
||||||
<InputContainer ref={modalRef} isOpen={isOpen}>
|
|
||||||
<MessageInput
|
|
||||||
ref={textareaRef}
|
|
||||||
value={message}
|
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
placeholder="메시지를 입력하세요..."
|
|
||||||
rows={1}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SendButton
|
|
||||||
onClick={handleSendMessage}
|
|
||||||
disabled={!message.trim() || isSending}
|
|
||||||
>
|
|
||||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
|
|
||||||
</svg>
|
|
||||||
</SendButton>
|
|
||||||
</InputContainer>
|
|
||||||
</ModalOverlay>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AIMessageInput;
|
|
||||||
|
|
||||||
const ModalOverlay = styled.div`
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
display: ${props => props.isOpen ? 'flex' : 'none'};
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 1000;
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 메인 컨테이너
|
|
||||||
const InputContainer = styled.div`
|
|
||||||
width: 90%;
|
|
||||||
max-width: 600px;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
border-radius: 12px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
animation: ${props => props.isOpen ? 'slideUp 0.3s ease-out' : 'none'};
|
|
||||||
|
|
||||||
@keyframes slideUp {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(20px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 메시지 입력 영역
|
|
||||||
const MessageInput = styled.textarea`
|
|
||||||
width: 100%;
|
|
||||||
min-height: 60px;
|
|
||||||
max-height: 200px;
|
|
||||||
padding: 16px 60px 16px 16px;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
resize: none;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1.5;
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: #9e9ea7;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 전송 버튼
|
|
||||||
const SendButton = styled.button`
|
|
||||||
position: absolute;
|
|
||||||
bottom: 12px;
|
|
||||||
right: 12px;
|
|
||||||
width: 38px;
|
|
||||||
height: 38px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #5436DA;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #4527D0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
background-color: #DADCE0;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
fill: white;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 메뉴 버튼 (세로 점 세개)
|
|
||||||
const MenuButton = styled.button`
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
border: none;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dot {
|
|
||||||
width: 4px;
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #666;
|
|
||||||
margin: 2px 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -97,20 +97,20 @@ const LogDetailModal = ({ detailView,
|
|||||||
const allChangedItems = [];
|
const allChangedItems = [];
|
||||||
|
|
||||||
changedData.forEach((item, itemIndex) => {
|
changedData.forEach((item, itemIndex) => {
|
||||||
if (item.changed && Array.isArray(item.changed)) {
|
if (item.domain.changed && Array.isArray(item.domain.changed)) {
|
||||||
item.changed.forEach((changedItem) => {
|
item.domain.changed.forEach((changedItem) => {
|
||||||
allChangedItems.push({
|
allChangedItems.push({
|
||||||
...changedItem,
|
...changedItem,
|
||||||
// 어떤 데이터 항목에서 온 것인지 구분하기 위한 정보 추가
|
// 어떤 데이터 항목에서 온 것인지 구분하기 위한 정보 추가
|
||||||
sourceIndex: itemIndex,
|
sourceIndex: itemIndex,
|
||||||
sourceInfo: {
|
sourceInfo: {
|
||||||
dbType: item.dbType,
|
dbType: item.dbType,
|
||||||
timestamp: item.timestamp,
|
logTime: item.logTime,
|
||||||
operationType: item.operationType,
|
operationType: item.domain.operationType,
|
||||||
historyType: item.historyType,
|
historyType: item.historyType,
|
||||||
tableName: item.tableName,
|
tableName: item.tableName,
|
||||||
tranId: item.tranId,
|
tranId: item.tranId,
|
||||||
userId: item.userId
|
worker: item.worker
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -164,8 +164,8 @@ const LogDetailModal = ({ detailView,
|
|||||||
<td>{item.fieldName}</td>
|
<td>{item.fieldName}</td>
|
||||||
<td>{formatValue(item.newValue)}</td>
|
<td>{formatValue(item.newValue)}</td>
|
||||||
<td>{formatValue(item.oldValue)}</td>
|
<td>{formatValue(item.oldValue)}</td>
|
||||||
<td>{item.sourceInfo.userId}</td>
|
<td>{item.sourceInfo.worker}</td>
|
||||||
<td>{convertKTC(item.sourceInfo.timestamp, false)}</td>
|
<td>{convertKTC(item.sourceInfo.logTime, false)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ const ModalBg = styled(motion.div)`
|
|||||||
min-width: 1080px;
|
min-width: 1080px;
|
||||||
display: ${props => (props.$view === 'hidden' ? 'none' : 'block')};
|
display: ${props => (props.$view === 'hidden' ? 'none' : 'block')};
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: auto;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ModalContainer = styled.div`
|
const ModalContainer = styled.div`
|
||||||
@@ -52,9 +54,18 @@ const ModalWrapper = styled(motion.div)`
|
|||||||
min-width: ${props => props.min || 'auto'};
|
min-width: ${props => props.min || 'auto'};
|
||||||
padding: ${props => props.$padding || '30px'};
|
padding: ${props => props.$padding || '30px'};
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
max-height: 90%;
|
max-height: calc(100vh - 40px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);
|
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
|
/*모바일*/
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
min-width: unset;
|
||||||
|
max-width: calc(100vw - 20px);
|
||||||
|
max-height: calc(100vh - 20px);
|
||||||
|
padding: ${props => props.$padding || '20px'};
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Modal = ({ children, $padding, min, $view, $bgcolor }) => {
|
const Modal = ({ children, $padding, min, $view, $bgcolor }) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, Fragment, useEffect } from 'react';
|
import React, { useState, Fragment, useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Button from '../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
FormStatusWarning,
|
FormStatusWarning,
|
||||||
FormButtonContainer,
|
FormButtonContainer,
|
||||||
} from '../../styles/ModuleComponents';
|
} from '../../styles/ModuleComponents';
|
||||||
import { Modal, SingleDatePicker, SingleTimePicker } from '../common';
|
import { DetailLayout, Modal, SingleDatePicker, SingleTimePicker } from '../common';
|
||||||
import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../assets/data/adminConstants';
|
import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../assets/data/adminConstants';
|
||||||
import { convertKTCDate } from '../../utils';
|
import { convertKTCDate } from '../../utils';
|
||||||
import {
|
import {
|
||||||
@@ -64,16 +64,6 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
}
|
}
|
||||||
}, [modalType, content]);
|
}, [modalType, content]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if(modalType === TYPE_REGISTRY && configData?.length > 0){
|
|
||||||
setResultData(prev => ({
|
|
||||||
...prev,
|
|
||||||
round_count: configData[0].default_round_count,
|
|
||||||
round_time: configData[0].round_time
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}, [modalType, configData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (checkCondition()) {
|
if (checkCondition()) {
|
||||||
setIsNullValue(false);
|
setIsNullValue(false);
|
||||||
@@ -82,84 +72,12 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
}
|
}
|
||||||
}, [resultData]);
|
}, [resultData]);
|
||||||
|
|
||||||
// 시작 날짜 변경 핸들러
|
const opGameMode = useMemo(() => {
|
||||||
const handleStartDateChange = (date) => {
|
return gameModeData?.map(item => ({
|
||||||
if (!date) return;
|
value: item.id,
|
||||||
|
name: `${item.desc}(${item.id})`
|
||||||
const newDate = new Date(date);
|
})) || [];
|
||||||
|
}, [gameModeData]);
|
||||||
if(resultData.repeat_type !== NONE && resultData.event_end_dt){
|
|
||||||
const endDate = new Date(resultData.event_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) {
|
|
||||||
showToast('DATE_START_DIFF_END_WARNING', {type: alertTypes.warning});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setResultData(prev => ({
|
|
||||||
...prev,
|
|
||||||
event_start_dt: newDate
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 시작 시간 변경 핸들러
|
|
||||||
const handleStartTimeChange = (time) => {
|
|
||||||
if (!time) return;
|
|
||||||
|
|
||||||
const newDateTime = resultData.event_start_dt
|
|
||||||
? new Date(resultData.event_start_dt)
|
|
||||||
: new Date();
|
|
||||||
|
|
||||||
newDateTime.setHours(
|
|
||||||
time.getHours(),
|
|
||||||
time.getMinutes(),
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
setResultData(prev => ({
|
|
||||||
...prev,
|
|
||||||
event_start_dt: newDateTime
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 종료 날짜 변경 핸들러
|
|
||||||
const handleEndDateChange = (date) => {
|
|
||||||
if (!date || !resultData.event_start_dt) return;
|
|
||||||
|
|
||||||
const startDate = new Date(resultData.event_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) {
|
|
||||||
showToast('DATE_START_DIFF_END_WARNING', {type: alertTypes.warning});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setResultData(prev => ({
|
|
||||||
...prev,
|
|
||||||
event_end_dt: endDate
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConfigChange = (e) => {
|
|
||||||
const config = configData.find(data => String(data.id) === String(e.target.value));
|
|
||||||
if (config) {
|
|
||||||
setResultData({
|
|
||||||
...resultData,
|
|
||||||
config_id: config.id,
|
|
||||||
round_time: config.round_time
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
showToast('Config not found for value:', e.target.value, {type: alertTypes.warning});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
setDetailData({});
|
setDetailData({});
|
||||||
@@ -190,6 +108,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 +132,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 +157,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});
|
||||||
@@ -244,6 +180,7 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
return (
|
return (
|
||||||
resultData.event_start_dt !== ''
|
resultData.event_start_dt !== ''
|
||||||
&& resultData.group_id !== ''
|
&& resultData.group_id !== ''
|
||||||
|
&& resultData.game_mode_id > 0
|
||||||
&& resultData.event_name !== ''
|
&& resultData.event_name !== ''
|
||||||
&& (resultData.repeat_type === 'NONE' || (resultData.repeat_type !== 'NONE' && resultData.event_end_dt !== ''))
|
&& (resultData.repeat_type === 'NONE' || (resultData.repeat_type !== 'NONE' && resultData.event_end_dt !== ''))
|
||||||
);
|
);
|
||||||
@@ -265,117 +202,122 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const itemGroups = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
row: 0,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'text',
|
||||||
|
key: 'group_id',
|
||||||
|
label: '그룹 ID',
|
||||||
|
disabled: !isView('group'),
|
||||||
|
width: '150px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 0,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'text',
|
||||||
|
key: 'event_name',
|
||||||
|
label: '이벤트명',
|
||||||
|
disabled: !isView('name'),
|
||||||
|
width: '250px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 1,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'date',
|
||||||
|
key: 'event_start_dt',
|
||||||
|
label: '시작일시',
|
||||||
|
disabled: !isView('start_dt'),
|
||||||
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
|
width: '200px',
|
||||||
|
showTime: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 1,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'number',
|
||||||
|
key: 'event_operation_time',
|
||||||
|
label: '진행시간(분)',
|
||||||
|
disabled: !isView('operation_time'),
|
||||||
|
width: '100px',
|
||||||
|
min: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 3,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'select',
|
||||||
|
key: 'repeat_type',
|
||||||
|
label: '반복',
|
||||||
|
disabled: !isView('repeat'),
|
||||||
|
width: '150px',
|
||||||
|
options: battleRepeatType
|
||||||
|
},
|
||||||
|
...(resultData?.repeat_type !== 'NONE' ? [{
|
||||||
|
row: 3,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'date',
|
||||||
|
key: 'event_end_dt',
|
||||||
|
label: '종료일',
|
||||||
|
disabled: !isView('end_dt'),
|
||||||
|
format: 'YYYY-MM-DD',
|
||||||
|
width: '200px'
|
||||||
|
}] : []),
|
||||||
|
{
|
||||||
|
row: 4,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'select',
|
||||||
|
key: 'game_mode_id',
|
||||||
|
label: '게임 모드',
|
||||||
|
disabled: !isView('mode'),
|
||||||
|
width: '150px',
|
||||||
|
options: opGameMode
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 4,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'select',
|
||||||
|
key: 'hot_time',
|
||||||
|
label: '핫타임',
|
||||||
|
disabled: !isView('hot'),
|
||||||
|
width: '150px',
|
||||||
|
options: battleEventHotTime.map(value => ({
|
||||||
|
value: value,
|
||||||
|
name: `${value}배`
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal min="760px" $view={detailView}>
|
<Modal min="760px" $view={detailView}>
|
||||||
<Title $align="center">{isView('registry') ? "전투시스템 이벤트 등록" : isView('modify') ? "전투시스템 이벤트 수정" : "전투시스템 이벤트 상세"}</Title>
|
<Title $align="center">{isView('registry') ? "전투시스템 이벤트 등록" : isView('modify') ? "전투시스템 이벤트 수정" : "전투시스템 이벤트 상세"}</Title>
|
||||||
<MessageWrapper>
|
<DetailLayout
|
||||||
<FormRowGroup>
|
itemGroups={itemGroups}
|
||||||
<FormLabel>그룹 ID</FormLabel>
|
formData={resultData}
|
||||||
<FormInput
|
onChange={setResultData}
|
||||||
type="text"
|
disabled={false}
|
||||||
disabled={!isView('group')}
|
columnCount={4}
|
||||||
width='150px'
|
/>
|
||||||
value={resultData?.group_id}
|
|
||||||
onChange={e => setResultData({ ...resultData, group_id: e.target.value })}
|
|
||||||
/>
|
|
||||||
<FormLabel>이벤트명</FormLabel>
|
|
||||||
<FormInput
|
|
||||||
type="text"
|
|
||||||
disabled={!isView('name')}
|
|
||||||
width='300px'
|
|
||||||
value={resultData?.event_name}
|
|
||||||
onChange={e => setResultData({ ...resultData, event_name: e.target.value })}
|
|
||||||
/>
|
|
||||||
</FormRowGroup>
|
|
||||||
<FormRowGroup>
|
|
||||||
<SingleDatePicker
|
|
||||||
label="시작일자"
|
|
||||||
disabled={!isView('start_dt')}
|
|
||||||
dateLabel="시작 일자"
|
|
||||||
onDateChange={handleStartDateChange}
|
|
||||||
selectedDate={resultData?.event_start_dt}
|
|
||||||
/>
|
|
||||||
<SingleTimePicker
|
|
||||||
label="시작시간"
|
|
||||||
disabled={!isView('start_dt')}
|
|
||||||
selectedTime={resultData?.event_start_dt}
|
|
||||||
onTimeChange={handleStartTimeChange}
|
|
||||||
/>
|
|
||||||
</FormRowGroup>
|
|
||||||
<FormRowGroup>
|
|
||||||
<FormLabel>반복</FormLabel>
|
|
||||||
<SelectInput value={resultData?.repeat_type} onChange={e => setResultData({ ...resultData, repeat_type: e.target.value })} disabled={!isView('repeat')} width="150px">
|
|
||||||
{battleRepeatType.map((data, index) => (
|
|
||||||
<option key={index} value={data.value}>
|
|
||||||
{data.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
{resultData?.repeat_type !== 'NONE' &&
|
|
||||||
<SingleDatePicker
|
|
||||||
label="종료일자"
|
|
||||||
disabled={!isView('end_dt')}
|
|
||||||
dateLabel="종료 일자"
|
|
||||||
onDateChange={handleEndDateChange}
|
|
||||||
selectedDate={resultData?.event_end_dt}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</FormRowGroup>
|
|
||||||
<FormRowGroup>
|
|
||||||
{/*<FormLabel>라운드 시간</FormLabel>*/}
|
|
||||||
{/*<SelectInput value={resultData.config_id} onChange={handleConfigChange} disabled={!isView('config')} width="200px">*/}
|
|
||||||
{/* {configData && configData?.map((data, index) => (*/}
|
|
||||||
{/* <option key={index} value={data.id}>*/}
|
|
||||||
{/* {data.desc}({data.id})*/}
|
|
||||||
{/* </option>*/}
|
|
||||||
{/* ))}*/}
|
|
||||||
{/*</SelectInput>*/}
|
|
||||||
<FormLabel>라운드 수</FormLabel>
|
|
||||||
<SelectInput value={resultData.round_count} onChange={e => setResultData({ ...resultData, round_count: e.target.value })} disabled={!isView('round')} width="100px">
|
|
||||||
{battleEventRoundCount.map((data, index) => (
|
|
||||||
<option key={index} value={data}>
|
|
||||||
{data}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
</FormRowGroup>
|
|
||||||
<FormRowGroup>
|
|
||||||
{/*<FormLabel>배정 포드</FormLabel>*/}
|
|
||||||
{/*<SelectInput value={resultData.reward_group_id} onChange={e => setResultData({ ...resultData, reward_group_id: e.target.value })} disabled={!isView('reward')} width="200px">*/}
|
|
||||||
{/* {rewardData && rewardData?.map((data, index) => (*/}
|
|
||||||
{/* <option key={index} value={data.group_id}>*/}
|
|
||||||
{/* {data.desc}({data.group_id})*/}
|
|
||||||
{/* </option>*/}
|
|
||||||
{/* ))}*/}
|
|
||||||
{/*</SelectInput>*/}
|
|
||||||
<FormLabel>게임 모드</FormLabel>
|
|
||||||
<SelectInput value={resultData.game_mode_id} onChange={e => setResultData({ ...resultData, game_mode_id: e.target.value })} disabled={!isView('mode')} width="200px">
|
|
||||||
{gameModeData && gameModeData?.map((data, index) => (
|
|
||||||
<option key={index} value={data.id}>
|
|
||||||
{data.desc}({data.id})
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
<FormLabel>핫타임</FormLabel>
|
|
||||||
<SelectInput value={resultData.hot_time} onChange={e => setResultData({ ...resultData, hot_time: e.target.value })} disabled={!isView('hot')} width="100px">
|
|
||||||
{battleEventHotTime.map((data, index) => (
|
|
||||||
<option key={index} value={data}>
|
|
||||||
{data}배
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
</FormRowGroup>
|
|
||||||
|
|
||||||
{!isView() && isNullValue && <SearchBarAlert $marginTop="25px" $align="right">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
|
{!isView() && isNullValue && <SearchBarAlert $marginTop="25px" $align="right">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
|
||||||
</MessageWrapper>
|
|
||||||
|
|
||||||
<BtnWrapper $gap="10px" $marginTop="10px">
|
<BtnWrapper $gap="10px" $marginTop="10px">
|
||||||
<FormStatusBar>
|
<FormStatusBar>
|
||||||
<FormStatusLabel>
|
<FormStatusLabel>
|
||||||
@@ -432,9 +374,10 @@ export const initData = {
|
|||||||
reward_group_id: 1,
|
reward_group_id: 1,
|
||||||
round_count: 1,
|
round_count: 1,
|
||||||
hot_time: 1,
|
hot_time: 1,
|
||||||
game_mode_id: 1,
|
game_mode_id: '',
|
||||||
event_start_dt: '',
|
event_start_dt: '',
|
||||||
event_end_dt: ''
|
event_end_dt: '',
|
||||||
|
event_operation_time: 10
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BattleEventModal;
|
export default BattleEventModal;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect, Fragment } from 'react';
|
import { useState, useEffect, Fragment } from 'react';
|
||||||
|
|
||||||
import { Title, SelectInput, BtnWrapper, TextInput, Label, InputLabel, Textarea, SearchBarAlert } from '../../styles/Components';
|
import { Input, Button as AntButton, Select, Alert, Space, Card, Row, Col } from 'antd';
|
||||||
|
import { Title, BtnWrapper } from '../../styles/Components';
|
||||||
import Button from '../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import Modal from '../common/modal/Modal';
|
import Modal from '../common/modal/Modal';
|
||||||
import { EventIsItem, EventModify } from '../../apis';
|
import { EventIsItem, EventModify } from '../../apis';
|
||||||
@@ -10,16 +11,13 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { authType, benItems, commonStatus, currencyItemCode } from '../../assets/data';
|
import { authType, benItems, commonStatus, currencyItemCode } from '../../assets/data';
|
||||||
import {
|
import {
|
||||||
AppendRegistBox, AppendRegistTable, AreaBtnClose,
|
DetailRegistInfo, DetailState
|
||||||
BtnDelete, DetailInputItem, DetailInputRow,
|
|
||||||
DetailModalWrapper, RegistGroup, DetailRegistInfo, DetailState,
|
|
||||||
Item, ItemList, LangArea
|
|
||||||
} from '../../styles/ModuleComponents';
|
} from '../../styles/ModuleComponents';
|
||||||
import { convertKTC, combineDateTime, timeDiffMinute, convertKTCDate } from '../../utils';
|
import { convertKTC, 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 } from '../../assets/data/types';
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
import { DetailLayout } from '../common';
|
||||||
|
|
||||||
const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData }) => {
|
const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData }) => {
|
||||||
const userInfo = useRecoilValue(authList);
|
const userInfo = useRecoilValue(authList);
|
||||||
@@ -31,21 +29,14 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
|
|||||||
const id = content && content.id;
|
const id = content && content.id;
|
||||||
const updateAuth = userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate);
|
const updateAuth = userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate);
|
||||||
|
|
||||||
const [time, setTime] = useState({
|
const [activeLanguage, setActiveLanguage] = useState('KO');
|
||||||
start_hour: '00',
|
|
||||||
start_min: '00',
|
|
||||||
end_hour: '00',
|
|
||||||
end_min: '00',
|
|
||||||
}); //시간 정보
|
|
||||||
|
|
||||||
const [item, setItem] = useState('');
|
const [item, setItem] = useState('');
|
||||||
const [itemCount, setItemCount] = useState('');
|
const [itemCount, setItemCount] = useState(1);
|
||||||
const [resource, setResource] = useState('19010001');
|
const [resource, setResource] = useState('19010001');
|
||||||
const [resourceCount, setResourceCount] = useState('');
|
const [resourceCount, setResourceCount] = useState(1);
|
||||||
|
|
||||||
const [resultData, setResultData] = useState({});
|
const [resultData, setResultData] = useState({});
|
||||||
|
|
||||||
const [isNullValue, setIsNullValue] = useState(false);
|
|
||||||
// 과거 판단
|
// 과거 판단
|
||||||
const [isPast, setIsPast] = useState(false);
|
const [isPast, setIsPast] = useState(false);
|
||||||
const [isChanged, setIsChanged] = useState(false);
|
const [isChanged, setIsChanged] = useState(false);
|
||||||
@@ -65,13 +56,8 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
|
|||||||
event_type: content.event_type,
|
event_type: content.event_type,
|
||||||
mail_list: content.mail_list,
|
mail_list: content.mail_list,
|
||||||
item_list: content.item_list,
|
item_list: content.item_list,
|
||||||
});
|
status: content.status,
|
||||||
|
delete_desc: content.delete_desc
|
||||||
setTime({ ...time,
|
|
||||||
start_hour: String(start_dt_KTC.getHours()).padStart(2, '0'),
|
|
||||||
start_min: String(start_dt_KTC.getMinutes()).padStart(2, '0'),
|
|
||||||
end_hour: String(end_dt_KTC.getHours()).padStart(2, '0'),
|
|
||||||
end_min: String(end_dt_KTC.getMinutes()).padStart(2, '0')
|
|
||||||
});
|
});
|
||||||
|
|
||||||
start_dt_KTC < (new Date) ? setIsPast(true) : setIsPast(false);
|
start_dt_KTC < (new Date) ? setIsPast(true) : setIsPast(false);
|
||||||
@@ -90,29 +76,97 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
|
|||||||
}
|
}
|
||||||
}, [updateAuth, isPast]);
|
}, [updateAuth, isPast]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (conditionCheck()) {
|
|
||||||
setIsNullValue(false);
|
|
||||||
} else {
|
|
||||||
setIsNullValue(true);
|
|
||||||
}
|
|
||||||
}, [resultData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setItemCheckMsg('');
|
setItemCheckMsg('');
|
||||||
}, [item]);
|
}, [item]);
|
||||||
|
|
||||||
// 아이템 수량 숫자 체크
|
const getLanguageTabItems = () => {
|
||||||
const handleItemCount = e => {
|
return resultData.mail_list?.map(mail => ({
|
||||||
if (e.target.value === '0' || e.target.value === '-0') {
|
key: mail.language,
|
||||||
setItemCount('1');
|
label: mail.language,
|
||||||
e.target.value = '1';
|
children: (
|
||||||
} else if (e.target.value < 0) {
|
<div style={{ padding: '10px', minHeight: '400px', height: 'auto' }}>
|
||||||
let plusNum = Math.abs(e.target.value);
|
<Row gutter={[16, 24]}>
|
||||||
setItemCount(plusNum);
|
<Col span={24}>
|
||||||
} else {
|
<div>
|
||||||
setItemCount(e.target.value);
|
<label style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '8px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: 'rgba(0, 0, 0, 0.85)',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>
|
||||||
|
제목 <span style={{ color: '#ff4d4f' }}>*</span>
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={mail.title || ''}
|
||||||
|
placeholder="우편 제목을 입력하세요"
|
||||||
|
maxLength={30}
|
||||||
|
readOnly={isReadOnly}
|
||||||
|
onChange={(e) => updateMailData(mail.language, 'title', e.target.value.trimStart())}
|
||||||
|
showCount
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<div>
|
||||||
|
<label style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '8px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: 'rgba(0, 0, 0, 0.85)',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>
|
||||||
|
내용 <span style={{ color: '#ff4d4f' }}>*</span>
|
||||||
|
</label>
|
||||||
|
<Input.TextArea
|
||||||
|
value={mail.content || ''}
|
||||||
|
placeholder="우편 내용을 입력하세요"
|
||||||
|
readOnly={isReadOnly}
|
||||||
|
rows={8}
|
||||||
|
maxLength={2000}
|
||||||
|
showCount
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.value.length > 2000) return;
|
||||||
|
updateMailData(mail.language, 'content', e.target.value.trimStart());
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
resize: 'vertical',
|
||||||
|
minHeight: '200px',
|
||||||
|
maxHeight: '400px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
closable: resultData.mail_list?.length > 1 && !isReadOnly, // 마지막 하나가 아니고 읽기전용이 아닐 때만 삭제 가능
|
||||||
|
})) || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateMailData = (language, field, value) => {
|
||||||
|
const updatedMailList = resultData.mail_list.map(mail =>
|
||||||
|
mail.language === language
|
||||||
|
? { ...mail, [field]: value }
|
||||||
|
: mail
|
||||||
|
);
|
||||||
|
setResultData({ ...resultData, mail_list: updatedMailList });
|
||||||
|
setIsChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabClose = (targetKey) => {
|
||||||
|
if (resultData.mail_list.length <= 1) return;
|
||||||
|
|
||||||
|
const filterList = resultData.mail_list.filter(el => el.language !== targetKey);
|
||||||
|
setResultData({ ...resultData, mail_list: filterList });
|
||||||
|
|
||||||
|
// 삭제된 탭이 현재 활성 탭이었다면 첫 번째 탭으로 변경
|
||||||
|
if (activeLanguage === targetKey) {
|
||||||
|
setActiveLanguage(filterList[0]?.language || 'KO');
|
||||||
}
|
}
|
||||||
|
setIsChanged(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 아이템 추가
|
// 아이템 추가
|
||||||
@@ -152,19 +206,6 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
|
|||||||
setResultData({ ...resultData, item_list: filterList });
|
setResultData({ ...resultData, item_list: filterList });
|
||||||
};
|
};
|
||||||
|
|
||||||
// 자원 수량 숫자 체크
|
|
||||||
const handleResourceCount = e => {
|
|
||||||
if (e.target.value === '0' || e.target.value === '-0') {
|
|
||||||
setResourceCount('1');
|
|
||||||
e.target.value = '1';
|
|
||||||
} else if (e.target.value < 0) {
|
|
||||||
let plusNum = Math.abs(e.target.value);
|
|
||||||
setResourceCount(plusNum);
|
|
||||||
} else {
|
|
||||||
setResourceCount(e.target.value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 자원 추가
|
// 자원 추가
|
||||||
const handleResourceList = (e) => {
|
const handleResourceList = (e) => {
|
||||||
if(resource.length === 0 || resourceCount.length === 0) return;
|
if(resource.length === 0 || resourceCount.length === 0) return;
|
||||||
@@ -186,45 +227,9 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
|
|||||||
setResourceCount('');
|
setResourceCount('');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 입력창 삭제
|
|
||||||
const onLangDelete = language => {
|
|
||||||
let filterList = resultData.mail_list && resultData.mail_list.filter(el => el.language !== language);
|
|
||||||
|
|
||||||
if (filterList.length === 1) setBtnValidation(true);
|
|
||||||
|
|
||||||
setIsChanged(true);
|
|
||||||
setResultData({ ...resultData, mail_list: filterList });
|
|
||||||
};
|
|
||||||
|
|
||||||
// 날짜 처리
|
|
||||||
const handleDateChange = (data, type) => {
|
|
||||||
const date = new Date(data);
|
|
||||||
setResultData({
|
|
||||||
...resultData,
|
|
||||||
[`${type}_dt`]: combineDateTime(date, time[`${type}_hour`], time[`${type}_min`]),
|
|
||||||
});
|
|
||||||
setIsChanged(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 시간 처리
|
|
||||||
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`]),
|
|
||||||
});
|
|
||||||
setIsChanged(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 확인 버튼 후 다 초기화
|
// 확인 버튼 후 다 초기화
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
setBtnValidation(false);
|
setBtnValidation(false);
|
||||||
setIsNullValue(false);
|
|
||||||
setIsChanged(false);
|
setIsChanged(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -281,6 +286,197 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 아이템 목록 렌더링 컴포넌트
|
||||||
|
const renderItemList = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{resultData.item_list && resultData.item_list.length > 0 && (
|
||||||
|
<Space wrap>
|
||||||
|
{resultData.item_list.map((data, index) => (
|
||||||
|
<Card
|
||||||
|
key={index}
|
||||||
|
title={data.item_name}
|
||||||
|
size="small"
|
||||||
|
extra={
|
||||||
|
!isReadOnly && (
|
||||||
|
<AntButton
|
||||||
|
type="text"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
onClick={() => onItemRemove(index)}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</AntButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
style={{ minWidth: '150px' }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div>{data.item}</div>
|
||||||
|
<div>수량: {data.item_cnt}</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 아이템 추가 컴포넌트
|
||||||
|
const renderItemAdd = () => {
|
||||||
|
return (
|
||||||
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
|
<Input
|
||||||
|
placeholder="Item Meta id 입력"
|
||||||
|
value={item}
|
||||||
|
onChange={(e) => setItem(e.target.value.trimStart())}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
style={{ width: '200px' }}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="수량"
|
||||||
|
value={itemCount}
|
||||||
|
onChange={(e) => setItemCount(e.target.value)}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
style={{ width: '120px' }}
|
||||||
|
min={1}
|
||||||
|
/>
|
||||||
|
<AntButton
|
||||||
|
type="primary"
|
||||||
|
onClick={handleItemList}
|
||||||
|
disabled={itemCount.length === 0 || item.length === 0 || isReadOnly}
|
||||||
|
>
|
||||||
|
추가
|
||||||
|
</AntButton>
|
||||||
|
</Space.Compact>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 자원 추가 컴포넌트
|
||||||
|
const renderResourceAdd = () => {
|
||||||
|
return (
|
||||||
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
|
<Select
|
||||||
|
value={resource}
|
||||||
|
onChange={setResource}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
style={{ width: '200px' }}
|
||||||
|
placeholder="자원 선택"
|
||||||
|
>
|
||||||
|
{currencyItemCode.map((data, index) => (
|
||||||
|
<Select.Option key={index} value={data.value}>
|
||||||
|
{data.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="수량"
|
||||||
|
value={resourceCount}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
onChange={(e) => setResourceCount(e.target.value)}
|
||||||
|
style={{ width: '120px' }}
|
||||||
|
min={1}
|
||||||
|
/>
|
||||||
|
<AntButton
|
||||||
|
type="primary"
|
||||||
|
onClick={handleResourceList}
|
||||||
|
disabled={resourceCount.length === 0 || resource.length === 0 || isReadOnly}
|
||||||
|
>
|
||||||
|
추가
|
||||||
|
</AntButton>
|
||||||
|
</Space.Compact>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemGroups = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
row: 0,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'dateRange',
|
||||||
|
keys: {
|
||||||
|
start: 'start_dt',
|
||||||
|
end: 'end_dt'
|
||||||
|
},
|
||||||
|
label: '이벤트 기간',
|
||||||
|
disabled: isReadOnly,
|
||||||
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
|
showTime: true,
|
||||||
|
startLabel: '시작 일시',
|
||||||
|
endLabel: '종료 일시'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 0,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 1,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'status',
|
||||||
|
label: '이벤트 상태',
|
||||||
|
render: () => detailState(resultData.status)
|
||||||
|
},
|
||||||
|
...(resultData.status === commonStatus.delete ? [{
|
||||||
|
row: 0,
|
||||||
|
col: 3,
|
||||||
|
colSpan: 1,
|
||||||
|
type: 'display',
|
||||||
|
key: 'delete_desc',
|
||||||
|
label: '삭제 사유',
|
||||||
|
value: resultData.delete_desc || ''
|
||||||
|
}] : [{
|
||||||
|
row: 0,
|
||||||
|
col: 3,
|
||||||
|
colSpan: 1,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'empty_space',
|
||||||
|
label: '',
|
||||||
|
render: () => <div></div>
|
||||||
|
}]),
|
||||||
|
{
|
||||||
|
row: 1,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'tab',
|
||||||
|
key: 'language_tabs',
|
||||||
|
tabItems: getLanguageTabItems(),
|
||||||
|
activeKey: activeLanguage,
|
||||||
|
onTabChange: setActiveLanguage,
|
||||||
|
onTabClose: handleTabClose
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 2,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'item_add',
|
||||||
|
label: '아이템 추가',
|
||||||
|
render: renderItemAdd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 3,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'resource_add',
|
||||||
|
label: '자원 추가',
|
||||||
|
render: renderResourceAdd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 4,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'item_list',
|
||||||
|
render: renderItemList
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal min="960px" $view={detailView}>
|
<Modal min="960px" $view={detailView}>
|
||||||
@@ -297,201 +493,22 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
|
|||||||
)}
|
)}
|
||||||
</DetailRegistInfo>
|
</DetailRegistInfo>
|
||||||
}
|
}
|
||||||
<DetailModalWrapper>
|
|
||||||
{content &&
|
|
||||||
<RegistGroup>
|
|
||||||
<DetailInputRow>
|
|
||||||
<DateTimeInput
|
|
||||||
title="이벤트 기간"
|
|
||||||
dateName="시작 일자"
|
|
||||||
selectedDate={convertKTCDate(content.start_dt)}
|
|
||||||
handleSelectedDate={data => handleDateChange(data, 'start')}
|
|
||||||
onChange={e => handleTimeChange(e, 'start')}
|
|
||||||
|
|
||||||
/>
|
<DetailLayout
|
||||||
<DateTimeInput
|
itemGroups={itemGroups}
|
||||||
dateName="종료 일자"
|
formData={resultData}
|
||||||
selectedDate={convertKTCDate(content.end_dt)}
|
onChange={setResultData}
|
||||||
handleSelectedDate={data => handleDateChange(data, 'end')}
|
disabled={false}
|
||||||
onChange={e => handleTimeChange(e, 'end')}
|
columnCount={4}
|
||||||
/>
|
/>
|
||||||
</DetailInputRow>
|
|
||||||
<DetailInputRow>
|
|
||||||
<DetailInputItem>
|
|
||||||
<InputLabel>이벤트 상태</InputLabel>
|
|
||||||
<div>{detailState(content.status)}</div>
|
|
||||||
</DetailInputItem>
|
|
||||||
{content.status === commonStatus.delete &&
|
|
||||||
<DetailInputItem>
|
|
||||||
<InputLabel>삭제 사유</InputLabel>
|
|
||||||
<div>{content.delete_desc}</div>
|
|
||||||
</DetailInputItem>
|
|
||||||
}
|
|
||||||
</DetailInputRow>
|
|
||||||
|
|
||||||
</RegistGroup>
|
{itemCheckMsg && (
|
||||||
}
|
<Alert
|
||||||
{resultData.mail_list &&
|
message={itemCheckMsg}
|
||||||
resultData.mail_list.map(data => {
|
type="error"
|
||||||
return (
|
style={{ marginTop: '8px', width: '300px' }}
|
||||||
<Fragment key={data.language}>
|
/>
|
||||||
<AppendRegistBox>
|
)}
|
||||||
<LangArea>
|
|
||||||
언어 : {data.language}
|
|
||||||
{btnValidation === false && !isReadOnly ? (
|
|
||||||
<AreaBtnClose
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
onLangDelete(data.language);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<AreaBtnClose opacity="10%" />
|
|
||||||
)}
|
|
||||||
</LangArea>
|
|
||||||
<AppendRegistTable>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th width="120">
|
|
||||||
<Label>제목</Label>
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<DetailInputItem>
|
|
||||||
<TextInput
|
|
||||||
placeholder="우편 제목 입력"
|
|
||||||
maxLength="30"
|
|
||||||
id={data.language}
|
|
||||||
value={data.title}
|
|
||||||
readOnly={isReadOnly}
|
|
||||||
onChange={e => {
|
|
||||||
let list = [...resultData.mail_list];
|
|
||||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
|
||||||
list[findIndex].title = e.target.value.trimStart();
|
|
||||||
|
|
||||||
setResultData({ ...resultData, mail_list: list });
|
|
||||||
setIsChanged(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DetailInputItem>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<Label>내용</Label>
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<Textarea
|
|
||||||
value={data.content}
|
|
||||||
readOnly={isReadOnly}
|
|
||||||
id={data.language}
|
|
||||||
onChange={e => {
|
|
||||||
if (e.target.value.length > 2000) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let list = [...resultData.mail_list];
|
|
||||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
|
||||||
list[findIndex].content = e.target.value.trimStart();
|
|
||||||
|
|
||||||
setResultData({ ...resultData, mail_list: list });
|
|
||||||
setIsChanged(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</AppendRegistTable>
|
|
||||||
</AppendRegistBox>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<AppendRegistBox>
|
|
||||||
<AppendRegistTable>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th width="120">
|
|
||||||
<Label>아이템 첨부</Label>
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<DetailInputItem>
|
|
||||||
<TextInput
|
|
||||||
placeholder="Item Meta id 입력"
|
|
||||||
value={item}
|
|
||||||
onChange={e => {
|
|
||||||
let list = [];
|
|
||||||
list = e.target.value.trimStart();
|
|
||||||
setItem(list);
|
|
||||||
}}
|
|
||||||
disabled={isReadOnly}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
placeholder="수량"
|
|
||||||
value={itemCount}
|
|
||||||
type="number"
|
|
||||||
onChange={e => handleItemCount(e)}
|
|
||||||
width="90px"
|
|
||||||
disabled={isReadOnly}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
text="추가"
|
|
||||||
theme={itemCount.length === 0 || item.length === 0 ? 'disable' : 'search'}
|
|
||||||
handleClick={handleItemList}
|
|
||||||
/>
|
|
||||||
{itemCheckMsg && <SearchBarAlert>{itemCheckMsg}</SearchBarAlert>}
|
|
||||||
</DetailInputItem>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th width="120">
|
|
||||||
<Label>자원 첨부</Label>
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<DetailInputItem>
|
|
||||||
<SelectInput onChange={e => setResource(e.target.value)} value={resource} disabled={isReadOnly}>
|
|
||||||
{currencyItemCode.map((data, index) => (
|
|
||||||
<option key={index} value={data.value}>
|
|
||||||
{data.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
<TextInput
|
|
||||||
placeholder="수량"
|
|
||||||
type="number"
|
|
||||||
value={resourceCount}
|
|
||||||
disabled={isReadOnly}
|
|
||||||
onChange={e => handleResourceCount(e)}
|
|
||||||
width="200px"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
text="추가"
|
|
||||||
theme={resourceCount.length === 0 || resource.length === 0 ? 'disable' : 'search'}
|
|
||||||
handleClick={handleResourceList}
|
|
||||||
width="100px"
|
|
||||||
height="35px"
|
|
||||||
errorMessage={isReadOnly} />
|
|
||||||
</DetailInputItem>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{resultData.item_list && (
|
|
||||||
<ItemList>
|
|
||||||
{resultData.item_list.map((data, index) => {
|
|
||||||
return (
|
|
||||||
<Item key={index}>
|
|
||||||
<span>
|
|
||||||
{data.item_name}[{data.item}] ({data.item_cnt})
|
|
||||||
</span>
|
|
||||||
{!isReadOnly && <BtnDelete onClick={() => onItemRemove(index)}></BtnDelete>}
|
|
||||||
</Item>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ItemList>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</AppendRegistTable>
|
|
||||||
</AppendRegistBox>
|
|
||||||
</DetailModalWrapper>
|
|
||||||
<BtnWrapper $justify="flex-end" $gap="10px" $paddingTop="20px">
|
<BtnWrapper $justify="flex-end" $gap="10px" $paddingTop="20px">
|
||||||
<Button
|
<Button
|
||||||
text="확인"
|
text="확인"
|
||||||
|
|||||||
311
src/components/modal/EventModal.js
Normal file
311
src/components/modal/EventModal.js
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
import React, { useState, Fragment, useEffect, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import Button from '../common/button/Button';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Title,
|
||||||
|
BtnWrapper,
|
||||||
|
SearchBarAlert,
|
||||||
|
} from '../../styles/Components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FormStatusBar,
|
||||||
|
FormStatusLabel,
|
||||||
|
FormStatusWarning,
|
||||||
|
FormButtonContainer,
|
||||||
|
} from '../../styles/ModuleComponents';
|
||||||
|
import { DetailLayout, Modal} from '../common';
|
||||||
|
import { TYPE_MODIFY, TYPE_REGISTRY } from '../../assets/data/adminConstants';
|
||||||
|
import { convertKTCDate } from '../../utils';
|
||||||
|
import {
|
||||||
|
opCommonStatus,
|
||||||
|
} from '../../assets/data/options';
|
||||||
|
import { alertTypes, commonStatus } from '../../assets/data/types';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { useLoading } from '../../context/LoadingProvider';
|
||||||
|
import { EventModify, EventSingleRegist } from '../../apis';
|
||||||
|
|
||||||
|
const EventModal = ({ modalType, detailView, handleDetailView, content, setDetailData, eventActionData }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
const { showToast, showModal } = useAlert();
|
||||||
|
const {withLoading} = useLoading();
|
||||||
|
|
||||||
|
const [isNullValue, setIsNullValue] = useState(false);
|
||||||
|
const [resultData, setResultData] = useState(initData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(modalType === TYPE_MODIFY && content && Object.keys(content).length > 0){
|
||||||
|
setResultData({
|
||||||
|
id: content.id,
|
||||||
|
title: content.title,
|
||||||
|
global_event_action_id: content.global_event_action_id,
|
||||||
|
personal_event_action_id: content.personal_event_action_id,
|
||||||
|
status: content.status,
|
||||||
|
max_point: content.max_point,
|
||||||
|
start_dt: convertKTCDate(content.start_dt),
|
||||||
|
end_dt: convertKTCDate(content.end_dt)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [modalType, content]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (checkCondition()) {
|
||||||
|
setIsNullValue(false);
|
||||||
|
} else {
|
||||||
|
setIsNullValue(true);
|
||||||
|
}
|
||||||
|
}, [resultData]);
|
||||||
|
|
||||||
|
const opEventActionMode = useMemo(() => {
|
||||||
|
return eventActionData?.map(item => ({
|
||||||
|
value: item.id,
|
||||||
|
name: `${item.description}(${item.id})`
|
||||||
|
})) || [];
|
||||||
|
}, [eventActionData]);
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
setDetailData({});
|
||||||
|
setResultData(initData);
|
||||||
|
handleDetailView();
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async (type, param = null) => {
|
||||||
|
switch (type) {
|
||||||
|
case "submit":
|
||||||
|
if (!checkCondition()) return;
|
||||||
|
|
||||||
|
const minAllowedTime = new Date(new Date().getTime() + 10 * 60000);
|
||||||
|
const startDt = resultData.start_dt;
|
||||||
|
const endDt = resultData.end_dt;
|
||||||
|
// if (modalType === TYPE_REGISTRY && startDt < minAllowedTime) {
|
||||||
|
// showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// if(resultData.repeat_type !== 'NONE' && !isValidDayRange(startDt, endDt)) {
|
||||||
|
// showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// //화면에 머물면서 상태는 안바꼈을 경우가 있기에 시작시간 지났을경우 차단
|
||||||
|
// if (modalType === TYPE_REGISTRY && startDt < new Date()) {
|
||||||
|
// showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
showModal(isView('modify') ? 'BATTLE_EVENT_UPDATE_CONFIRM' : 'BATTLE_EVENT_REGIST_CONFIRM', {
|
||||||
|
type: alertTypes.confirm,
|
||||||
|
onConfirm: () => handleSubmit('registConfirm')
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "registConfirm":
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
...resultData
|
||||||
|
};
|
||||||
|
|
||||||
|
if(isView('modify')){
|
||||||
|
await withLoading( async () => {
|
||||||
|
return await EventModify(token, content?.id, params);
|
||||||
|
}).then(data => {
|
||||||
|
if(data.result === "SUCCESS") {
|
||||||
|
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
|
||||||
|
}else{
|
||||||
|
showToast('UPDATE_FAIL', {type: alertTypes.error});
|
||||||
|
}
|
||||||
|
}).catch(reason => {
|
||||||
|
showToast('API_FAIL', {type: alertTypes.error});
|
||||||
|
}).finally(() => {
|
||||||
|
handleReset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
await withLoading( async () => {
|
||||||
|
return await EventSingleRegist(token, params);
|
||||||
|
}).then(data => {
|
||||||
|
if(data.result === "SUCCESS") {
|
||||||
|
showToast('REGIST_COMPLTE', {type: alertTypes.success});
|
||||||
|
}else{
|
||||||
|
showToast('REGIST_FAIL', {type: alertTypes.error});
|
||||||
|
}
|
||||||
|
}).catch(reason => {
|
||||||
|
showToast('API_FAIL', {type: alertTypes.error});
|
||||||
|
}).finally(() => {
|
||||||
|
handleReset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkCondition = () => {
|
||||||
|
return (
|
||||||
|
resultData.start_dt !== ''
|
||||||
|
&& resultData.end_dt !== ''
|
||||||
|
&& resultData.title !== ''
|
||||||
|
&& resultData.global_event_action_id > 0
|
||||||
|
&& resultData.personal_event_action_id > 0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isView = (label) => {
|
||||||
|
switch (label) {
|
||||||
|
case "modify":
|
||||||
|
return modalType === TYPE_MODIFY && (content?.status === commonStatus.wait);
|
||||||
|
case "registry":
|
||||||
|
case "mode":
|
||||||
|
return modalType === TYPE_REGISTRY
|
||||||
|
case "start_dt":
|
||||||
|
case "end_dt":
|
||||||
|
case "max_point":
|
||||||
|
case "name":
|
||||||
|
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY &&(content?.status === commonStatus.wait));
|
||||||
|
default:
|
||||||
|
return modalType === TYPE_MODIFY && (content?.status !== commonStatus.wait);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemGroups = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
row: 0,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'text',
|
||||||
|
key: 'title',
|
||||||
|
label: '이벤트명',
|
||||||
|
disabled: !isView('name'),
|
||||||
|
width: '300px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 1,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'date',
|
||||||
|
key: 'start_dt',
|
||||||
|
label: '시작일시',
|
||||||
|
disabled: !isView('start_dt'),
|
||||||
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
|
width: '200px',
|
||||||
|
showTime: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 1,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'date',
|
||||||
|
key: 'end_dt',
|
||||||
|
label: '종료일시',
|
||||||
|
disabled: !isView('end_dt'),
|
||||||
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
|
width: '200px',
|
||||||
|
showTime: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 4,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'select',
|
||||||
|
key: 'global_event_action_id',
|
||||||
|
label: '기여도 이벤트 모드',
|
||||||
|
disabled: !isView('mode'),
|
||||||
|
width: '150px',
|
||||||
|
options: opEventActionMode
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 4,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'number',
|
||||||
|
key: 'max_point',
|
||||||
|
label: '기여도 목표점수',
|
||||||
|
disabled: !isView('max_point'),
|
||||||
|
width: '150px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 5,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'select',
|
||||||
|
key: 'personal_event_action_id',
|
||||||
|
label: '개인제작 이벤트 모드',
|
||||||
|
disabled: !isView('mode'),
|
||||||
|
width: '150px',
|
||||||
|
options: opEventActionMode
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal min="760px" $view={detailView}>
|
||||||
|
<Title $align="center">{isView('registry') ? "통합 이벤트 등록" : isView('modify') ? "통합 이벤트 수정" : "통합 이벤트 상세"}</Title>
|
||||||
|
<DetailLayout
|
||||||
|
itemGroups={itemGroups}
|
||||||
|
formData={resultData}
|
||||||
|
onChange={setResultData}
|
||||||
|
disabled={false}
|
||||||
|
columnCount={4}
|
||||||
|
/>
|
||||||
|
{!isView() && isNullValue && <SearchBarAlert $marginTop="25px" $align="right">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
|
||||||
|
<BtnWrapper $gap="10px" $marginTop="10px">
|
||||||
|
<FormStatusBar>
|
||||||
|
<FormStatusLabel>
|
||||||
|
현재상태: {opCommonStatus.find(data => data.value === content?.status)?.name || "등록"}
|
||||||
|
</FormStatusLabel>
|
||||||
|
<FormStatusWarning>
|
||||||
|
{isView('registry') ? '' : t('EVENT_MODAL_STATUS_WARNING')}
|
||||||
|
</FormStatusWarning>
|
||||||
|
</FormStatusBar>
|
||||||
|
<FormButtonContainer $gap="5px">
|
||||||
|
{isView() ?
|
||||||
|
<Button
|
||||||
|
text="확인"
|
||||||
|
name="확인버튼"
|
||||||
|
theme="line"
|
||||||
|
handleClick={() => handleReset()}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
text="취소"
|
||||||
|
theme="line"
|
||||||
|
handleClick={() => showModal('CANCEL_CONFIRM', {
|
||||||
|
type: alertTypes.confirm,
|
||||||
|
onConfirm: () => handleReset()
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
text={isView('modify') ? "수정" : "등록"}
|
||||||
|
name="등록버튼"
|
||||||
|
theme={
|
||||||
|
checkCondition()
|
||||||
|
? 'primary'
|
||||||
|
: 'disable'
|
||||||
|
}
|
||||||
|
handleClick={() => handleSubmit('submit')}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</FormButtonContainer>
|
||||||
|
</BtnWrapper>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initData = {
|
||||||
|
title: '',
|
||||||
|
start_dt: '',
|
||||||
|
end_dt: '',
|
||||||
|
global_event_action_id: '',
|
||||||
|
personal_event_action_id: '',
|
||||||
|
max_point: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EventModal;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, Fragment, useEffect } from 'react';
|
import React, { useState, Fragment, useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Button from '../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import Loading from '../common/Loading';
|
import Loading from '../common/Loading';
|
||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
NoticeInputItem2, BoxWrapper, FormStatusBar, FormStatusLabel, FormStatusWarning, FormButtonContainer,
|
NoticeInputItem2, BoxWrapper, FormStatusBar, FormStatusLabel, FormStatusWarning, FormButtonContainer,
|
||||||
} from '../../styles/ModuleComponents';
|
} from '../../styles/ModuleComponents';
|
||||||
import { modalTypes } from '../../assets/data';
|
import { modalTypes } from '../../assets/data';
|
||||||
import {DynamicModal, Modal, DateTimeRangePicker} from '../common';
|
import { DynamicModal, Modal, DateTimeRangePicker, DetailLayout } from '../common';
|
||||||
import { LandAuctionModify, LandAuctionSingleRegist } from '../../apis';
|
import { LandAuctionModify, LandAuctionSingleRegist } from '../../apis';
|
||||||
import {
|
import {
|
||||||
AUCTION_MIN_MINUTE_TIME,
|
AUCTION_MIN_MINUTE_TIME,
|
||||||
@@ -36,6 +36,7 @@ import { convertKTCDate } from '../../utils';
|
|||||||
import { msToMinutes } from '../../utils/date';
|
import { msToMinutes } from '../../utils/date';
|
||||||
import { useAlert } from '../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import { alertTypes } from '../../assets/data/types';
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
import { battleEventHotTime, battleRepeatType } from '../../assets/data/options';
|
||||||
|
|
||||||
const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, setDetailData, landData, buildingData }) => {
|
const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, setDetailData, landData, buildingData }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -62,10 +63,8 @@ const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
currency_type: content.currency_type,
|
currency_type: content.currency_type,
|
||||||
start_price: content.start_price,
|
start_price: content.start_price,
|
||||||
resv_start_dt: convertKTCDate(content.resv_start_dt),
|
resv_start_dt: convertKTCDate(content.resv_start_dt),
|
||||||
resv_end_dt: convertKTCDate(content.resv_end_dt),
|
|
||||||
auction_start_dt: convertKTCDate(content.auction_start_dt),
|
auction_start_dt: convertKTCDate(content.auction_start_dt),
|
||||||
auction_end_dt: convertKTCDate(content.auction_end_dt),
|
auction_end_dt: convertKTCDate(content.auction_end_dt),
|
||||||
message_list: content.message_list,
|
|
||||||
});
|
});
|
||||||
const land = landData.find(land => land.id === parseInt(content.land_id));
|
const land = landData.find(land => land.id === parseInt(content.land_id));
|
||||||
setSelectLand(land);
|
setSelectLand(land);
|
||||||
@@ -86,76 +85,26 @@ const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
}
|
}
|
||||||
}, [resetDateTime]);
|
}, [resetDateTime]);
|
||||||
|
|
||||||
// 입력 수량 처리
|
const opLand = useMemo(() => {
|
||||||
const handleCount = e => {
|
return landData?.map(item => ({
|
||||||
const regex = /^\d*\.?\d{0,2}$/;
|
value: item.id,
|
||||||
if (!regex.test(e.target.value) && e.target.value !== '-') {
|
name: `${item.name}(${item.id})`
|
||||||
return;
|
})) || [];
|
||||||
}
|
}, [landData]);
|
||||||
|
|
||||||
let count = 0;
|
const handleLand = (value, key, currentFormData) => {
|
||||||
if (e.target.value === '-0') {
|
let land_id = value;
|
||||||
count = 1;
|
|
||||||
} else if (e.target.value < 0) {
|
|
||||||
let plusNum = Math.abs(e.target.value);
|
|
||||||
count = plusNum;
|
|
||||||
} else{
|
|
||||||
count = e.target.value;
|
|
||||||
}
|
|
||||||
setResultData((prevState) => ({
|
|
||||||
...prevState,
|
|
||||||
start_price: count,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReservationChange = {
|
|
||||||
start: (date) => {
|
|
||||||
setResultData(prev => ({ ...prev, resv_start_dt: date }));
|
|
||||||
},
|
|
||||||
end: (date) => {
|
|
||||||
setResultData(prev => ({ ...prev, resv_end_dt: date }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAuctionChange = {
|
|
||||||
start: (date) => {
|
|
||||||
setResultData(prev => ({ ...prev, auction_start_dt: date }));
|
|
||||||
},
|
|
||||||
end: (date) => {
|
|
||||||
setResultData(prev => ({ ...prev, auction_end_dt: date }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 입력 글자 제한
|
|
||||||
const handleInputData = e => {
|
|
||||||
if (e.target.value.length > 250) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const updatedMessages = resultData.message_list.map(msg =>
|
|
||||||
msg.language === message_lang
|
|
||||||
? { ...msg, content: e.target.value.trimStart() }
|
|
||||||
: msg
|
|
||||||
);
|
|
||||||
|
|
||||||
setResultData(prev => ({
|
|
||||||
...prev,
|
|
||||||
message_list: updatedMessages
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 언어 선택
|
|
||||||
const handleLanguage = e => {
|
|
||||||
setMessage_lang(e.target.value);
|
|
||||||
if(!resultData.message_list.some(({language}) => language === e.target.value))
|
|
||||||
setResultData({ ...resultData, message_list: [...resultData.message_list, {language: e.target.value, content: ''}] })
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleLand = e => {
|
|
||||||
const land_id = e.target.value;
|
|
||||||
const land = landData.find(land => land.id === parseInt(land_id));
|
const land = landData.find(land => land.id === parseInt(land_id));
|
||||||
const instance = buildingData.find(building => building.id === parseInt(land.buildingId))?.socket;
|
const instance = buildingData.find(building => building.id === parseInt(land.buildingId))?.socket;
|
||||||
setSelectLand(land);
|
setSelectLand(land);
|
||||||
setResultData({ ...resultData, land_id: land_id, land_name: land.name, land_size: land.size, land_socket: instance });
|
const updatedData = {
|
||||||
|
...currentFormData,
|
||||||
|
land_name: land.name,
|
||||||
|
land_size: land.size,
|
||||||
|
land_socket: instance
|
||||||
|
};
|
||||||
|
setResultData(updatedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
@@ -256,13 +205,12 @@ const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
&& resultData.resv_start_dt !== ''
|
&& resultData.resv_start_dt !== ''
|
||||||
&& resultData.resv_end_dt !== ''
|
&& resultData.resv_end_dt !== ''
|
||||||
&& resultData.land_id !== ''
|
&& resultData.land_id !== ''
|
||||||
// && resultData.message_list?.every(data => data.content !== '')
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isView = (label) => {
|
const isView = (label) => {
|
||||||
switch (label) {
|
switch (label) {
|
||||||
case "recv":
|
case "resv":
|
||||||
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY && content?.status === landAuctionStatusType.wait);
|
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY && content?.status === landAuctionStatusType.wait);
|
||||||
case "auction":
|
case "auction":
|
||||||
case "price":
|
case "price":
|
||||||
@@ -282,131 +230,117 @@ const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const itemGroups = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
row: 0,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'select',
|
||||||
|
key: 'land_id',
|
||||||
|
label: '랜드선택',
|
||||||
|
disabled: !isView('registry'),
|
||||||
|
width: '400px',
|
||||||
|
options: opLand,
|
||||||
|
handler: handleLand
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 1,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'display',
|
||||||
|
key: 'land_name',
|
||||||
|
label: '랜드 이름',
|
||||||
|
width: '400px',
|
||||||
|
placeholder: '랜드를 선택하세요'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 2,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'display',
|
||||||
|
key: 'land_size',
|
||||||
|
label: '랜드 크기',
|
||||||
|
placeholder: '랜드를 선택하세요',
|
||||||
|
width: '200px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 2,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'display',
|
||||||
|
key: 'land_socket',
|
||||||
|
label: '인스턴스 수',
|
||||||
|
placeholder: '랜드를 선택하세요',
|
||||||
|
width: '200px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 3,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'select',
|
||||||
|
key: 'currency_type',
|
||||||
|
label: '입찰재화',
|
||||||
|
disabled: true,
|
||||||
|
width: '200px',
|
||||||
|
options: CurrencyType
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 3,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'number',
|
||||||
|
key: 'start_price',
|
||||||
|
label: '입찰시작가',
|
||||||
|
disabled: !isView('price'),
|
||||||
|
width: '200px',
|
||||||
|
min: 0,
|
||||||
|
step: 0.01
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 4,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'date',
|
||||||
|
key: 'resv_start_dt',
|
||||||
|
label: '예약시작일',
|
||||||
|
disabled: !isView('resv'),
|
||||||
|
width: '200px',
|
||||||
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
|
showTime: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 5,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'dateRange',
|
||||||
|
keys: {
|
||||||
|
start: 'auction_start_dt',
|
||||||
|
end: 'auction_end_dt'
|
||||||
|
},
|
||||||
|
label: '경매기간',
|
||||||
|
disabled: !isView('auction'),
|
||||||
|
width: '400px',
|
||||||
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
|
showTime: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal min="760px" $view={detailView}>
|
<Modal min="760px" $view={detailView}>
|
||||||
<Title $align="center">{isView('registry') ? "랜드 경매 등록" : isView('modify') ? "랜드 경매 수정" : "랜드 경매 상세"}</Title>
|
<Title $align="center">{isView('registry') ? "랜드 경매 등록" : isView('modify') ? "랜드 경매 수정" : "랜드 경매 상세"}</Title>
|
||||||
<MessageWrapper>
|
<DetailLayout
|
||||||
<FormRowGroup>
|
itemGroups={itemGroups}
|
||||||
<FormLabel>랜드선택</FormLabel>
|
formData={resultData}
|
||||||
<SelectInput value={resultData.land_id} onChange={e => handleLand(e)} disabled={!isView('registry')} width="400px">
|
onChange={setResultData}
|
||||||
{landData && landData.map((data, index) => (
|
disabled={false}
|
||||||
<option key={index} value={data.id}>
|
columnCount={4}
|
||||||
{data.name}({data.id})
|
/>
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
</FormRowGroup>
|
|
||||||
<FormRowGroup>
|
|
||||||
<FormLabel>랜드 이름</FormLabel>
|
|
||||||
<FormInput
|
|
||||||
type="text"
|
|
||||||
disabled={true}
|
|
||||||
width='400px'
|
|
||||||
value={resultData?.land_name}
|
|
||||||
/>
|
|
||||||
</FormRowGroup>
|
|
||||||
<FormRowGroup>
|
|
||||||
<FormLabel>랜드 크기</FormLabel>
|
|
||||||
<FormInput
|
|
||||||
type="text"
|
|
||||||
disabled={true}
|
|
||||||
width='200px'
|
|
||||||
value={resultData?.land_size}
|
|
||||||
/>
|
|
||||||
<FormLabel>인스턴스 수</FormLabel>
|
|
||||||
<FormInput
|
|
||||||
type="text"
|
|
||||||
disabled={true}
|
|
||||||
width='200px'
|
|
||||||
value={resultData?.land_socket}
|
|
||||||
/>
|
|
||||||
</FormRowGroup>
|
|
||||||
|
|
||||||
<FormRowGroup>
|
|
||||||
<FormLabel>입찰 재화</FormLabel>
|
|
||||||
<SelectInput value={resultData.currency_type} width='200px' disabled={true} >
|
|
||||||
{CurrencyType.map((data, index) => (
|
|
||||||
<option key={index} value={data.value}>
|
|
||||||
{data.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
<FormLabel>입찰시작가</FormLabel>
|
|
||||||
<FormInput
|
|
||||||
type="number"
|
|
||||||
name="price"
|
|
||||||
value={resultData.start_price}
|
|
||||||
step={"0.01"}
|
|
||||||
min={0}
|
|
||||||
width='200px'
|
|
||||||
disabled={!isView('price')}
|
|
||||||
onChange={e => handleCount(e)}
|
|
||||||
/>
|
|
||||||
</FormRowGroup>
|
|
||||||
<DateTimeRangePicker
|
|
||||||
label="예약기간"
|
|
||||||
startDate={resultData.resv_start_dt}
|
|
||||||
endDate={resultData.resv_end_dt}
|
|
||||||
onStartDateChange={handleReservationChange.start}
|
|
||||||
onEndDateChange={handleReservationChange.end}
|
|
||||||
pastDate={new Date()}
|
|
||||||
disabled={!isView('recv')}
|
|
||||||
startLabel="시작 일자"
|
|
||||||
endLabel="종료 일자"
|
|
||||||
reset={resetDateTime}
|
|
||||||
/>
|
|
||||||
<DateTimeRangePicker
|
|
||||||
label="경매기간"
|
|
||||||
startDate={resultData.auction_start_dt}
|
|
||||||
endDate={resultData.auction_end_dt}
|
|
||||||
onStartDateChange={handleAuctionChange.start}
|
|
||||||
onEndDateChange={handleAuctionChange.end}
|
|
||||||
pastDate={new Date()}
|
|
||||||
disabled={!isView('auction')}
|
|
||||||
startLabel="시작 일자"
|
|
||||||
endLabel="종료 일자"
|
|
||||||
reset={resetDateTime}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/*<NoticeInputRow2>*/}
|
|
||||||
{/* <InputLabel>*/}
|
|
||||||
{/* 메세지 작성[경매 시작 5분전 공지 - 미구현]*/}
|
|
||||||
{/* </InputLabel>*/}
|
|
||||||
{/* <NoticeInputItem2>*/}
|
|
||||||
{/* <InputLabel>언어</InputLabel>*/}
|
|
||||||
{/* <SelectInput onChange={e => handleLanguage(e) } value={message_lang}>*/}
|
|
||||||
{/* {languageType.map((data, index) => (*/}
|
|
||||||
{/* <option key={index} value={data.value}>*/}
|
|
||||||
{/* {data.name}*/}
|
|
||||||
{/* </option>*/}
|
|
||||||
{/* ))}*/}
|
|
||||||
{/* </SelectInput>*/}
|
|
||||||
{/* </NoticeInputItem2>*/}
|
|
||||||
{/*</NoticeInputRow2>*/}
|
|
||||||
{/*<BoxWrapper>*/}
|
|
||||||
{/* {resultData.message_list.map(content => {*/}
|
|
||||||
{/* return (*/}
|
|
||||||
{/* <Fragment key={content.language}>*/}
|
|
||||||
{/* {message_lang === content.language && (*/}
|
|
||||||
{/* <FormTextAreaWrapper>*/}
|
|
||||||
{/* <FormTextArea*/}
|
|
||||||
{/* name="content"*/}
|
|
||||||
{/* id={content.language}*/}
|
|
||||||
{/* value={content.content}*/}
|
|
||||||
{/* onChange={e => handleInputData(e)}*/}
|
|
||||||
{/* maxLength={250}*/}
|
|
||||||
{/* disabled={!isView('message')}*/}
|
|
||||||
{/* />*/}
|
|
||||||
{/* </FormTextAreaWrapper>*/}
|
|
||||||
{/* )}*/}
|
|
||||||
{/* </Fragment>*/}
|
|
||||||
{/* );*/}
|
|
||||||
{/* })}*/}
|
|
||||||
{/*</BoxWrapper>*/}
|
|
||||||
{!isView() && isNullValue && <SearchBarAlert $marginTop="25px" $align="right">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
|
{!isView() && isNullValue && <SearchBarAlert $marginTop="25px" $align="right">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
|
||||||
</MessageWrapper>
|
|
||||||
|
|
||||||
<BtnWrapper $gap="10px" $marginTop="10px">
|
<BtnWrapper $gap="10px" $marginTop="10px">
|
||||||
<FormStatusBar>
|
<FormStatusBar>
|
||||||
<FormStatusLabel>
|
<FormStatusLabel>
|
||||||
@@ -463,14 +397,8 @@ export const initData = {
|
|||||||
currency_type: 'Calium',
|
currency_type: 'Calium',
|
||||||
start_price: 0,
|
start_price: 0,
|
||||||
resv_start_dt: '',
|
resv_start_dt: '',
|
||||||
resv_end_dt: '',
|
|
||||||
auction_start_dt: '',
|
auction_start_dt: '',
|
||||||
auction_end_dt: '',
|
auction_end_dt: ''
|
||||||
message_list: [
|
|
||||||
{ language: 'KO', content: '' },
|
|
||||||
{ language: 'EN', content: '' },
|
|
||||||
{ language: 'JA', content: '' },
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initLandData = {
|
export const initLandData = {
|
||||||
|
|||||||
@@ -2,6 +2,21 @@ import { styled } from 'styled-components';
|
|||||||
import RadioInput from '../common/input/Radio';
|
import RadioInput from '../common/input/Radio';
|
||||||
import React, { useState, useEffect, Fragment } from 'react';
|
import React, { useState, useEffect, Fragment } from 'react';
|
||||||
import CheckBox from '../common/input/CheckBox';
|
import CheckBox from '../common/input/CheckBox';
|
||||||
|
import {
|
||||||
|
Input,
|
||||||
|
Button as AntButton,
|
||||||
|
Select,
|
||||||
|
Alert,
|
||||||
|
Space,
|
||||||
|
Card,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Checkbox,
|
||||||
|
Radio,
|
||||||
|
DatePicker,
|
||||||
|
TimePicker
|
||||||
|
} from 'antd';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import { Title, SelectInput, BtnWrapper, TextInput, Label, InputLabel, DatePickerWrapper, Textarea} from '../../styles/Components';
|
import { Title, SelectInput, BtnWrapper, TextInput, Label, InputLabel, DatePickerWrapper, Textarea} from '../../styles/Components';
|
||||||
import Button from '../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
@@ -32,6 +47,7 @@ import { useLoading } from '../../context/LoadingProvider';
|
|||||||
import { alertTypes, currencyCodeTypes } from '../../assets/data/types';
|
import { alertTypes, currencyCodeTypes } from '../../assets/data/types';
|
||||||
import { userType2 } from '../../assets/data/options';
|
import { userType2 } from '../../assets/data/options';
|
||||||
import { STORAGE_MAIL_COPY } from '../../assets/data/adminConstants';
|
import { STORAGE_MAIL_COPY } from '../../assets/data/adminConstants';
|
||||||
|
import { DetailLayout } from '../common';
|
||||||
|
|
||||||
const MailDetailModal = ({ detailView, handleDetailView, content }) => {
|
const MailDetailModal = ({ detailView, handleDetailView, content }) => {
|
||||||
const userInfo = useRecoilValue(authList);
|
const userInfo = useRecoilValue(authList);
|
||||||
@@ -46,10 +62,11 @@ const MailDetailModal = ({ detailView, handleDetailView, content }) => {
|
|||||||
const [sendHour, setSendHour] = useState('00');
|
const [sendHour, setSendHour] = useState('00');
|
||||||
const [sendMin, setSendMin] = useState('00');
|
const [sendMin, setSendMin] = useState('00');
|
||||||
|
|
||||||
|
const [activeLanguage, setActiveLanguage] = useState('KO');
|
||||||
const [item, setItem] = useState('');
|
const [item, setItem] = useState('');
|
||||||
const [itemCount, setItemCount] = useState('');
|
const [itemCount, setItemCount] = useState(1);
|
||||||
const [resource, setResource] = useState(currencyCodeTypes.gold);
|
const [resource, setResource] = useState(currencyCodeTypes.gold);
|
||||||
const [resourceCount, setResourceCount] = useState('');
|
const [resourceCount, setResourceCount] = useState(1);
|
||||||
|
|
||||||
const [resultData, setResultData] = useState({
|
const [resultData, setResultData] = useState({
|
||||||
is_reserve: false,
|
is_reserve: false,
|
||||||
@@ -94,6 +111,7 @@ const MailDetailModal = ({ detailView, handleDetailView, content }) => {
|
|||||||
mail_list: content.mail_list,
|
mail_list: content.mail_list,
|
||||||
item_list: content.item_list,
|
item_list: content.item_list,
|
||||||
guid: content.target,
|
guid: content.target,
|
||||||
|
send_status: content.send_status,
|
||||||
file_name: content.receive_type === 'MULTIPLE' ? content.target : null
|
file_name: content.receive_type === 'MULTIPLE' ? content.target : null
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -122,6 +140,136 @@ const MailDetailModal = ({ detailView, handleDetailView, content }) => {
|
|||||||
}
|
}
|
||||||
},[updateAuth, content, resultData])
|
},[updateAuth, content, resultData])
|
||||||
|
|
||||||
|
// 발송 상태 렌더링
|
||||||
|
const renderSendStatus = () => {
|
||||||
|
const status = resultData.send_status;
|
||||||
|
let color = '';
|
||||||
|
let text = '';
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 'WAIT':
|
||||||
|
color = '#FAAD14';
|
||||||
|
text = '대기';
|
||||||
|
break;
|
||||||
|
case 'FINISH':
|
||||||
|
color = '#52c41a';
|
||||||
|
text = '완료';
|
||||||
|
break;
|
||||||
|
case 'FAIL':
|
||||||
|
color = '#ff4d4f';
|
||||||
|
text = '실패';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
color = '#d9d9d9';
|
||||||
|
text = status || '알 수 없음';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
padding: '2px 8px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
backgroundColor: color,
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '500'
|
||||||
|
}}>
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 탭 항목 생성
|
||||||
|
const getLanguageTabItems = () => {
|
||||||
|
return resultData.mail_list?.map(mail => ({
|
||||||
|
key: mail.language,
|
||||||
|
label: mail.language,
|
||||||
|
children: (
|
||||||
|
<div style={{ padding: '10px', minHeight: '400px', height: 'auto' }}>
|
||||||
|
<Row gutter={[16, 24]}>
|
||||||
|
<Col span={24}>
|
||||||
|
<div>
|
||||||
|
<label style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '8px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: 'rgba(0, 0, 0, 0.85)',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>
|
||||||
|
제목 <span style={{ color: '#ff4d4f' }}>*</span>
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={mail.title || ''}
|
||||||
|
placeholder="우편 제목을 입력하세요"
|
||||||
|
maxLength={30}
|
||||||
|
readOnly={isView}
|
||||||
|
onChange={(e) => updateMailData(mail.language, 'title', e.target.value.trimStart())}
|
||||||
|
showCount
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<div>
|
||||||
|
<label style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '8px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: 'rgba(0, 0, 0, 0.85)',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>
|
||||||
|
내용 <span style={{ color: '#ff4d4f' }}>*</span>
|
||||||
|
</label>
|
||||||
|
<Input.TextArea
|
||||||
|
value={mail.content || ''}
|
||||||
|
placeholder="우편 내용을 입력하세요"
|
||||||
|
readOnly={isView}
|
||||||
|
rows={6}
|
||||||
|
maxLength={2000}
|
||||||
|
showCount
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.value.length > 2000) return;
|
||||||
|
updateMailData(mail.language, 'content', e.target.value.trimStart());
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
resize: 'vertical',
|
||||||
|
minHeight: '200px',
|
||||||
|
maxHeight: '400px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
closable: resultData.mail_list?.length > 1 && !isView,
|
||||||
|
})) || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 메일 데이터 업데이트
|
||||||
|
const updateMailData = (language, field, value) => {
|
||||||
|
const updatedMailList = resultData.mail_list.map(mail =>
|
||||||
|
mail.language === language
|
||||||
|
? { ...mail, [field]: value }
|
||||||
|
: mail
|
||||||
|
);
|
||||||
|
setResultData({ ...resultData, mail_list: updatedMailList });
|
||||||
|
setIsChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 탭 삭제 핸들러
|
||||||
|
const handleTabClose = (targetKey) => {
|
||||||
|
if (resultData.mail_list.length <= 1) return;
|
||||||
|
|
||||||
|
const filterList = resultData.mail_list.filter(el => el.language !== targetKey);
|
||||||
|
setResultData({ ...resultData, mail_list: filterList });
|
||||||
|
|
||||||
|
if (activeLanguage === targetKey) {
|
||||||
|
setActiveLanguage(filterList[0]?.language || 'KO');
|
||||||
|
}
|
||||||
|
setIsChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
// 아이템 수량 숫자 체크
|
// 아이템 수량 숫자 체크
|
||||||
const handleItemCount = e => {
|
const handleItemCount = e => {
|
||||||
if (e.target.value === '0' || e.target.value === '-0') {
|
if (e.target.value === '0' || e.target.value === '-0') {
|
||||||
@@ -338,12 +486,282 @@ const MailDetailModal = ({ detailView, handleDetailView, content }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDateTimeChange = (datetime) => {
|
||||||
|
setResultData({ ...resultData, send_dt: datetime.toDate() });
|
||||||
|
setIsChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 아이템 목록 렌더링
|
||||||
|
const renderItemList = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{resultData.item_list && resultData.item_list.length > 0 ? (
|
||||||
|
<Space wrap>
|
||||||
|
{resultData.item_list.map((data, index) => (
|
||||||
|
<Card
|
||||||
|
key={index}
|
||||||
|
title={data.item_name}
|
||||||
|
size="small"
|
||||||
|
extra={
|
||||||
|
!isView && (
|
||||||
|
<AntButton
|
||||||
|
type="text"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
onClick={() => onItemRemove(index)}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</AntButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
style={{ minWidth: '150px' }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div>{data.item}</div>
|
||||||
|
<div>수량: {data.item_cnt}</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
) : (
|
||||||
|
<Alert message="등록된 아이템이 없습니다." type="info" showIcon />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 아이템 추가 컴포넌트
|
||||||
|
const renderItemAdd = () => {
|
||||||
|
return (
|
||||||
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
|
<Input
|
||||||
|
placeholder="Item Meta id 입력"
|
||||||
|
value={item}
|
||||||
|
onChange={(e) => setItem(e.target.value.trimStart())}
|
||||||
|
disabled={isView}
|
||||||
|
style={{ width: '200px' }}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="수량"
|
||||||
|
value={itemCount}
|
||||||
|
onChange={(e) => setItemCount(e.target.value)}
|
||||||
|
disabled={isView}
|
||||||
|
style={{ width: '120px' }}
|
||||||
|
min={1}
|
||||||
|
/>
|
||||||
|
<AntButton
|
||||||
|
type="primary"
|
||||||
|
onClick={handleItemList}
|
||||||
|
disabled={itemCount.length === 0 || item.length === 0 || isView}
|
||||||
|
>
|
||||||
|
추가
|
||||||
|
</AntButton>
|
||||||
|
</Space.Compact>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 자원 추가 컴포넌트
|
||||||
|
const renderResourceAdd = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Space.Compact style={{ width: '100%', marginBottom: '8px' }}>
|
||||||
|
<Select
|
||||||
|
value={resource}
|
||||||
|
onChange={setResource}
|
||||||
|
disabled={isView}
|
||||||
|
style={{ width: '200px' }}
|
||||||
|
placeholder="자원 선택"
|
||||||
|
>
|
||||||
|
{currencyItemCode.map((data, index) => (
|
||||||
|
<Select.Option key={index} value={data.value}>
|
||||||
|
{data.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="수량"
|
||||||
|
value={resourceCount}
|
||||||
|
disabled={isView}
|
||||||
|
onChange={(e) => setResourceCount(e.target.value)}
|
||||||
|
style={{ width: '120px' }}
|
||||||
|
min={1}
|
||||||
|
/>
|
||||||
|
<AntButton
|
||||||
|
type="primary"
|
||||||
|
onClick={handleResourceList}
|
||||||
|
disabled={resourceCount.length === 0 || resource.length === 0 || isView}
|
||||||
|
>
|
||||||
|
추가
|
||||||
|
</AntButton>
|
||||||
|
</Space.Compact>
|
||||||
|
{resource === currencyCodeTypes.calium && (
|
||||||
|
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||||
|
잔여 수량: {caliumTotalData}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 수신대상 렌더링
|
||||||
|
const renderReceiver = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
|
<div>
|
||||||
|
<Radio.Group value={resultData.receive_type} disabled>
|
||||||
|
<Space direction="vertical">
|
||||||
|
<Radio value="SINGLE">
|
||||||
|
단일: {resultData.receive_type === 'SINGLE' ? resultData.guid : ''}
|
||||||
|
</Radio>
|
||||||
|
<Radio value="MULTIPLE">
|
||||||
|
복수: {resultData.receive_type === 'MULTIPLE' ? excelFile : ''}
|
||||||
|
</Radio>
|
||||||
|
</Space>
|
||||||
|
</Radio.Group>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemGroups = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
row: 0,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 1,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'is_reserve',
|
||||||
|
render: () => (
|
||||||
|
<Checkbox
|
||||||
|
checked={resultData.is_reserve}
|
||||||
|
disabled
|
||||||
|
onChange={(e) => {
|
||||||
|
setResultData({ ...resultData, is_reserve: e.target.checked });
|
||||||
|
setIsChanged(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
예약 발송
|
||||||
|
</Checkbox>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
...(resultData.is_reserve ? [{
|
||||||
|
row: 0,
|
||||||
|
col: 1,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'send_dt',
|
||||||
|
label: '발송 시간',
|
||||||
|
render: () => (
|
||||||
|
<DatePicker
|
||||||
|
showTime
|
||||||
|
allowClear={false}
|
||||||
|
value={resultData.send_dt ? dayjs(resultData.send_dt) : null}
|
||||||
|
onChange={handleDateTimeChange}
|
||||||
|
disabled={!content?.is_reserve || isView}
|
||||||
|
format="YYYY-MM-DD HH:mm"
|
||||||
|
style={{ width: '200px' }}
|
||||||
|
disabledDate={(current) => current && current < dayjs().startOf('day')}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}] : []),
|
||||||
|
{
|
||||||
|
row: 1,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 1,
|
||||||
|
type: 'select',
|
||||||
|
key: 'mail_type',
|
||||||
|
label: '우편 타입',
|
||||||
|
value: resultData.mail_type,
|
||||||
|
disabled: isView,
|
||||||
|
options: [
|
||||||
|
{ value: 'SELECT', name: '타입 선택' },
|
||||||
|
...mailType.filter(data => data.value !== 'ALL')
|
||||||
|
],
|
||||||
|
handler: (value) => {
|
||||||
|
setResultData({ ...resultData, mail_type: value });
|
||||||
|
setIsChanged(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 1,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 1,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'send_status',
|
||||||
|
label: '발송상태',
|
||||||
|
render: renderSendStatus
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 2,
|
||||||
|
col: 1,
|
||||||
|
colSpan: 1,
|
||||||
|
type: 'select',
|
||||||
|
key: 'user_type',
|
||||||
|
label: '수신대상 타입',
|
||||||
|
value: resultData.user_type,
|
||||||
|
disabled: true,
|
||||||
|
options: userType2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 2,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'receiver',
|
||||||
|
label: '수신대상',
|
||||||
|
render: renderReceiver
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 3,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'tab',
|
||||||
|
key: 'language_tabs',
|
||||||
|
tabItems: getLanguageTabItems(),
|
||||||
|
activeKey: activeLanguage,
|
||||||
|
onTabChange: setActiveLanguage,
|
||||||
|
onTabClose: handleTabClose
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 4,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'item_add',
|
||||||
|
label: '아이템 추가',
|
||||||
|
render: renderItemAdd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 5,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'resource_add',
|
||||||
|
label: '자원 추가',
|
||||||
|
render: renderResourceAdd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 6,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'item_list',
|
||||||
|
render: renderItemList
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal min="960px" $view={detailView}>
|
<Modal min="960px" $view={detailView}>
|
||||||
<Title $align="center">우편 상세 정보</Title>
|
<Title $align="center">우편 상세 정보</Title>
|
||||||
{content && <>
|
{content && <RegistInfo>
|
||||||
<RegistInfo>
|
|
||||||
<span>등록자 : {content.create_by}</span>
|
<span>등록자 : {content.create_by}</span>
|
||||||
<span>등록일 : {convertKTC(content.create_dt, false)}</span>
|
<span>등록일 : {convertKTC(content.create_dt, false)}</span>
|
||||||
{typeof content.update_by !== 'undefined' && (
|
{typeof content.update_by !== 'undefined' && (
|
||||||
@@ -352,321 +770,14 @@ const MailDetailModal = ({ detailView, handleDetailView, content }) => {
|
|||||||
<span>수정일 : {convertKTC(content.update_dt, false)}</span>
|
<span>수정일 : {convertKTC(content.update_dt, false)}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</RegistInfo>
|
</RegistInfo>}
|
||||||
<ModalWrapper>
|
<DetailLayout
|
||||||
<RegistGroup>
|
itemGroups={itemGroups}
|
||||||
<InputRow>
|
formData={resultData}
|
||||||
<CheckBox
|
onChange={setResultData}
|
||||||
label="예약 발송"
|
disabled={false}
|
||||||
id="reserve"
|
columnCount={4}
|
||||||
checked={resultData && resultData.is_reserve}
|
/>
|
||||||
setData={e => {
|
|
||||||
setResultData({ ...resultData, is_reserve: e.target.checked });
|
|
||||||
setIsChanged(true);
|
|
||||||
}}
|
|
||||||
disabled={(content.is_reserve === false) || isView}
|
|
||||||
/>
|
|
||||||
{content.is_reserve === false ? (
|
|
||||||
<></>
|
|
||||||
) : (
|
|
||||||
resultData.is_reserve === true && (
|
|
||||||
<InputItem>
|
|
||||||
<InputLabel>발송 시간</InputLabel>
|
|
||||||
<InputGroup>
|
|
||||||
<DatePickerWrapper>
|
|
||||||
<DatePickerComponent
|
|
||||||
readOnly={(content.is_reserve === false) || isView}
|
|
||||||
name={initialData.send_dt}
|
|
||||||
selectedDate={resultData ? resultData.send_dt : initialData.send_dt}
|
|
||||||
handleSelectedDate={data => handleSelectedDate(data)}
|
|
||||||
pastDate={new Date()}
|
|
||||||
/>
|
|
||||||
</DatePickerWrapper>
|
|
||||||
<SelectInput
|
|
||||||
onChange={e => handleSendTime(e)}
|
|
||||||
id="hour"
|
|
||||||
disabled={(content.is_reserve === false) || isView}
|
|
||||||
value={
|
|
||||||
resultData && String(new Date(resultData.send_dt).getHours()) < 10
|
|
||||||
? '0' + String(new Date(resultData.send_dt).getHours())
|
|
||||||
: resultData && String(new Date(resultData.send_dt).getHours())
|
|
||||||
}>
|
|
||||||
{HourList.map(hour => (
|
|
||||||
<option value={hour} key={hour}>
|
|
||||||
{hour}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
<SelectInput
|
|
||||||
onChange={e => {
|
|
||||||
handleSendTime(e);
|
|
||||||
setIsChanged(true);
|
|
||||||
}}
|
|
||||||
id="min"
|
|
||||||
disabled={(content.is_reserve === false) || isView}
|
|
||||||
value={
|
|
||||||
resultData && String(new Date(resultData.send_dt).getMinutes()) < 10
|
|
||||||
? '0' + String(new Date(resultData.send_dt).getMinutes())
|
|
||||||
: resultData && String(new Date(resultData.send_dt).getMinutes())
|
|
||||||
}>
|
|
||||||
{MinuteList.map(min => (
|
|
||||||
<option value={min} key={min}>
|
|
||||||
{min}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
</InputGroup>
|
|
||||||
</InputItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
<InputItem>
|
|
||||||
<InputLabel>우편 타입</InputLabel>
|
|
||||||
<SelectInput
|
|
||||||
onChange={e => {
|
|
||||||
setResultData({ ...resultData, mail_type: e.target.value });
|
|
||||||
setIsChanged(true);
|
|
||||||
}}
|
|
||||||
value={resultData.mail_type}
|
|
||||||
disabled={isView}>
|
|
||||||
<option value="SELECT">타입 선택</option>
|
|
||||||
{mailType.filter(data => data.value !== 'ALL').map((data, index) => (
|
|
||||||
<option key={index} value={data.value}>
|
|
||||||
{data.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
</InputItem>
|
|
||||||
<InputItem>
|
|
||||||
<InputLabel>발송상태</InputLabel>
|
|
||||||
<div>
|
|
||||||
{initialData.send_status === 'WAIT' && <MailState>대기</MailState>}
|
|
||||||
{initialData.send_status === 'FINISH' && <MailState result="success">완료</MailState>}
|
|
||||||
{initialData.send_status === 'FAIL' && <MailState result="fail">실패</MailState>}
|
|
||||||
</div>
|
|
||||||
</InputItem>
|
|
||||||
</InputRow>
|
|
||||||
<MailReceiver>
|
|
||||||
<InputItem>
|
|
||||||
<InputLabel>수신대상</InputLabel>
|
|
||||||
<InputItem>
|
|
||||||
<SelectInput onChange={e => setResultData({ ...resultData, user_type: e.target.value })}
|
|
||||||
value={resultData.user_type}
|
|
||||||
disabled={true}>
|
|
||||||
{userType2.map((data, index) => (
|
|
||||||
<option key={index} value={data.value}>
|
|
||||||
{data.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
</InputItem>
|
|
||||||
<div>
|
|
||||||
<InputGroup>
|
|
||||||
<RadioInput
|
|
||||||
label="단일"
|
|
||||||
id="SINGLE"
|
|
||||||
name="receiver"
|
|
||||||
value="SINGLE"
|
|
||||||
disabled={true}
|
|
||||||
fontWeight="600"
|
|
||||||
checked={resultData.receive_type === 'SINGLE'}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
disabled={true}
|
|
||||||
value={resultData.receive_type === 'SINGLE' && resultData.guid !== '' ? resultData.guid : ''}
|
|
||||||
/>
|
|
||||||
</InputGroup>
|
|
||||||
<InputGroup>
|
|
||||||
<RadioInput
|
|
||||||
label="복수"
|
|
||||||
id="MULTIPLE"
|
|
||||||
name="receiver"
|
|
||||||
value="MULTIPLE"
|
|
||||||
fontWeight="600"
|
|
||||||
disabled={true}
|
|
||||||
/>
|
|
||||||
<MailRegistUploadBtn
|
|
||||||
disabled={true}
|
|
||||||
setResultData={setResultData}
|
|
||||||
resultData={resultData}
|
|
||||||
setExcelFile={setExcelFile}
|
|
||||||
handleDetailDelete={() => {}}
|
|
||||||
disabledBtn={true}
|
|
||||||
excelName={excelFile}
|
|
||||||
setExcelName={setExcelFile}
|
|
||||||
downloadData={downloadData}
|
|
||||||
status={initialData.send_status}
|
|
||||||
/>
|
|
||||||
</InputGroup>
|
|
||||||
</div>
|
|
||||||
</InputItem>
|
|
||||||
</MailReceiver>
|
|
||||||
</RegistGroup>
|
|
||||||
{resultData.mail_list &&
|
|
||||||
resultData?.mail_list?.map(data => {
|
|
||||||
return (
|
|
||||||
<Fragment key={data.language}>
|
|
||||||
<MailRegistBox>
|
|
||||||
<LangArea>
|
|
||||||
언어 : {data.language}
|
|
||||||
{btnValidation === false ? (
|
|
||||||
<BtnClose
|
|
||||||
disabled={true}
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
onLangDelete(data.language);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<BtnClose opacity="10%" />
|
|
||||||
)}
|
|
||||||
</LangArea>
|
|
||||||
<MailRegistTable>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th width="120">
|
|
||||||
<Label>제목</Label>
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<InputItem>
|
|
||||||
<TextInput
|
|
||||||
placeholder="우편 제목 입력"
|
|
||||||
maxLength="30"
|
|
||||||
id={data.language}
|
|
||||||
value={data.title}
|
|
||||||
readOnly={(content.is_reserve === false) || isView}
|
|
||||||
onChange={e => {
|
|
||||||
let list = [...resultData.mail_list];
|
|
||||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
|
||||||
list[findIndex].title = e.target.value.trimStart();
|
|
||||||
|
|
||||||
setResultData({ ...resultData, mail_list: list });
|
|
||||||
setIsChanged(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</InputItem>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<Label>내용</Label>
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<Textarea
|
|
||||||
value={data.content}
|
|
||||||
readOnly={isView}
|
|
||||||
id={data.language}
|
|
||||||
onChange={e => {
|
|
||||||
if (e.target.value.length > 2000) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let list = [...resultData.mail_list];
|
|
||||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
|
||||||
list[findIndex].content = e.target.value.trimStart();
|
|
||||||
|
|
||||||
setResultData({ ...resultData, mail_list: list });
|
|
||||||
setIsChanged(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</MailRegistTable>
|
|
||||||
</MailRegistBox>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<MailRegistBox>
|
|
||||||
<MailRegistTable>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th width="120">
|
|
||||||
<Label>아이템 첨부</Label>
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<InputItem>
|
|
||||||
<TextInput
|
|
||||||
placeholder="Item Meta id 입력"
|
|
||||||
value={item}
|
|
||||||
onChange={e => {
|
|
||||||
let list = [];
|
|
||||||
list = e.target.value.trimStart();
|
|
||||||
setItem(list);
|
|
||||||
}}
|
|
||||||
disabled={isView}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
placeholder="수량"
|
|
||||||
value={itemCount}
|
|
||||||
type="number"
|
|
||||||
onChange={e => handleItemCount(e)}
|
|
||||||
width="90px"
|
|
||||||
disabled={isView}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
text="추가"
|
|
||||||
theme={itemCount.length === 0 || item.length === 0 ? 'disable' : 'search'}
|
|
||||||
disabled={isView}
|
|
||||||
handleClick={handleItemList}
|
|
||||||
/>
|
|
||||||
</InputItem>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th width="120">
|
|
||||||
<Label>자원 첨부</Label>
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<InputItem>
|
|
||||||
<SelectInput onChange={e => setResource(e.target.value)} value={resource} disabled={isView}>
|
|
||||||
{currencyItemCode.map((data, index) => (
|
|
||||||
<option key={index} value={data.value}>
|
|
||||||
{data.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
<TextInput
|
|
||||||
placeholder="수량"
|
|
||||||
type="number"
|
|
||||||
value={resourceCount}
|
|
||||||
disabled={isView}
|
|
||||||
onChange={e => handleResourceCount(e)}
|
|
||||||
width="200px"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
text="추가"
|
|
||||||
theme={resourceCount.length === 0 || resource.length === 0 ? 'disable' : 'search'}
|
|
||||||
disabled={isView}
|
|
||||||
handleClick={handleResourceList}
|
|
||||||
width="100px"
|
|
||||||
height="35px"
|
|
||||||
/>
|
|
||||||
{resource === currencyCodeTypes.calium &&
|
|
||||||
<Label>(잔여 수량: {caliumTotalData})</Label>}
|
|
||||||
</InputItem>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{resultData.item_list && (
|
|
||||||
<ItemList>
|
|
||||||
{resultData.item_list.map((data, index) => {
|
|
||||||
return (
|
|
||||||
<Item key={index}>
|
|
||||||
<span>
|
|
||||||
{data.item_name}[{data.item}] ({data.item_cnt})
|
|
||||||
</span>
|
|
||||||
{!isView && <BtnDelete onClick={() => onItemRemove(index)}></BtnDelete>}
|
|
||||||
</Item>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ItemList>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</MailRegistTable>
|
|
||||||
</MailRegistBox>
|
|
||||||
</ModalWrapper>
|
|
||||||
</>}
|
|
||||||
<BtnWrapper $justify="flex-end" $gap="10px" $paddingTop="20px">
|
<BtnWrapper $justify="flex-end" $gap="10px" $paddingTop="20px">
|
||||||
<Button
|
<Button
|
||||||
text="확인"
|
text="확인"
|
||||||
|
|||||||
@@ -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,50 +160,43 @@ 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){
|
||||||
return true;
|
switch(fieldName){
|
||||||
default:
|
case 'order_id':
|
||||||
return false;
|
case 'end_dt':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
379
src/components/modal/RankingModal.js
Normal file
379
src/components/modal/RankingModal.js
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
import React, { useState, Fragment, useEffect, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import Button from '../common/button/Button';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Title,
|
||||||
|
BtnWrapper,
|
||||||
|
SearchBarAlert, SelectInput,
|
||||||
|
} from '../../styles/Components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FormInput,
|
||||||
|
FormLabel,
|
||||||
|
MessageWrapper,
|
||||||
|
FormRowGroup,
|
||||||
|
FormStatusBar,
|
||||||
|
FormStatusLabel,
|
||||||
|
FormStatusWarning,
|
||||||
|
FormButtonContainer,
|
||||||
|
} from '../../styles/ModuleComponents';
|
||||||
|
import { DetailLayout, Modal, SingleDatePicker, SingleTimePicker } from '../common';
|
||||||
|
import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../assets/data/adminConstants';
|
||||||
|
import { convertKTCDate } from '../../utils';
|
||||||
|
import {
|
||||||
|
battleEventHotTime,
|
||||||
|
battleEventRoundCount,
|
||||||
|
battleEventStatus,
|
||||||
|
battleRepeatType, opCommonStatus,
|
||||||
|
} from '../../assets/data/options';
|
||||||
|
import { BattleEventModify, BattleEventSingleRegist } from '../../apis/Battle';
|
||||||
|
import { alertTypes, battleEventStatusType, commonStatus } from '../../assets/data/types';
|
||||||
|
import { isValidDayRange } from '../../utils/date';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { useLoading } from '../../context/LoadingProvider';
|
||||||
|
import { RankingScheduleModify, RankingScheduleSingleRegist } from '../../apis';
|
||||||
|
|
||||||
|
const RankingModal = ({ modalType, detailView, handleDetailView, content, setDetailData, rankingData, eventActionData }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
const { showToast, showModal } = useAlert();
|
||||||
|
const {withLoading} = useLoading();
|
||||||
|
|
||||||
|
const [isNullValue, setIsNullValue] = useState(false);
|
||||||
|
const [resultData, setResultData] = useState(initData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(modalType === TYPE_MODIFY && content && Object.keys(content).length > 0){
|
||||||
|
setResultData({
|
||||||
|
guid: content.guid,
|
||||||
|
id: content.id,
|
||||||
|
title: content.title,
|
||||||
|
meta_id: content.meta_id,
|
||||||
|
event_action_id: content.event_action_id,
|
||||||
|
refresh_interval: content.refresh_interval,
|
||||||
|
initialization_interval: content.initialization_interval,
|
||||||
|
snapshot_interval: content.snapshot_interval,
|
||||||
|
status: content.status,
|
||||||
|
start_dt: convertKTCDate(content.start_dt),
|
||||||
|
end_dt: convertKTCDate(content.end_dt),
|
||||||
|
base_dt: convertKTCDate(content.base_dt),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [modalType, content]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (checkCondition()) {
|
||||||
|
setIsNullValue(false);
|
||||||
|
} else {
|
||||||
|
setIsNullValue(true);
|
||||||
|
}
|
||||||
|
}, [resultData]);
|
||||||
|
|
||||||
|
const opRankingMode = useMemo(() => {
|
||||||
|
return rankingData?.map(item => ({
|
||||||
|
value: item.id,
|
||||||
|
name: `${item.desc}(${item.id})`
|
||||||
|
})) || [];
|
||||||
|
}, [rankingData]);
|
||||||
|
|
||||||
|
const opEventActionMode = useMemo(() => {
|
||||||
|
return eventActionData?.map(item => ({
|
||||||
|
value: item.id,
|
||||||
|
name: `${item.description}(${item.id})`
|
||||||
|
})) || [];
|
||||||
|
}, [eventActionData]);
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
setDetailData({});
|
||||||
|
setResultData(initData);
|
||||||
|
handleDetailView();
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async (type, param = null) => {
|
||||||
|
switch (type) {
|
||||||
|
case "submit":
|
||||||
|
if (!checkCondition()) return;
|
||||||
|
|
||||||
|
// const minAllowedTime = new Date(new Date().getTime() + 10 * 60000);
|
||||||
|
// const startDt = resultData.event_start_dt;
|
||||||
|
// const endDt = resultData.event_end_dt;
|
||||||
|
// if (modalType === TYPE_REGISTRY && startDt < minAllowedTime) {
|
||||||
|
// showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// if(resultData.repeat_type !== 'NONE' && !isValidDayRange(startDt, endDt)) {
|
||||||
|
// showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// //화면에 머물면서 상태는 안바꼈을 경우가 있기에 시작시간 지났을경우 차단
|
||||||
|
// if (modalType === TYPE_REGISTRY && startDt < new Date()) {
|
||||||
|
// showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
showModal(isView('modify') ? 'SCHEDULE_UPDATE_CONFIRM' : 'SCHEDULE_REGIST_CONFIRM', {
|
||||||
|
type: alertTypes.confirm,
|
||||||
|
onConfirm: () => handleSubmit('registConfirm')
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "registConfirm":
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
...resultData
|
||||||
|
};
|
||||||
|
|
||||||
|
if(isView('modify')){
|
||||||
|
await withLoading( async () => {
|
||||||
|
return await RankingScheduleModify(token, content?.id, params);
|
||||||
|
}).then(data => {
|
||||||
|
if(data.result === "SUCCESS") {
|
||||||
|
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
|
||||||
|
}else{
|
||||||
|
showToast('UPDATE_FAIL', {type: alertTypes.error});
|
||||||
|
}
|
||||||
|
}).catch(reason => {
|
||||||
|
showToast('API_FAIL', {type: alertTypes.error});
|
||||||
|
}).finally(() => {
|
||||||
|
handleReset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
await withLoading( async () => {
|
||||||
|
return await RankingScheduleSingleRegist(token, params);
|
||||||
|
}).then(data => {
|
||||||
|
if(data.result === "SUCCESS") {
|
||||||
|
showToast('REGIST_COMPLTE', {type: alertTypes.success});
|
||||||
|
}else{
|
||||||
|
showToast('REGIST_FAIL', {type: alertTypes.error});
|
||||||
|
}
|
||||||
|
}).catch(reason => {
|
||||||
|
showToast('API_FAIL', {type: alertTypes.error});
|
||||||
|
}).finally(() => {
|
||||||
|
handleReset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkCondition = () => {
|
||||||
|
return (
|
||||||
|
resultData.start_dt !== ''
|
||||||
|
&& resultData.end_dt !== ''
|
||||||
|
&& resultData.base_dt !== ''
|
||||||
|
&& resultData.meta_id > 0
|
||||||
|
&& resultData.event_action_id > 0
|
||||||
|
&& resultData.title !== ''
|
||||||
|
&& resultData.refresh_interval > 0
|
||||||
|
&& resultData.initialization_interval > 0
|
||||||
|
&& resultData.snapshot_interval > 0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isView = (label) => {
|
||||||
|
switch (label) {
|
||||||
|
case "modify":
|
||||||
|
return modalType === TYPE_MODIFY && (content?.status === commonStatus.wait);
|
||||||
|
case "registry":
|
||||||
|
return modalType === TYPE_REGISTRY
|
||||||
|
case "start_dt":
|
||||||
|
case "end_dt":
|
||||||
|
case "base_dt":
|
||||||
|
case "name":
|
||||||
|
case "refresh_interval":
|
||||||
|
case "init_interval":
|
||||||
|
case "snapshot_interval":
|
||||||
|
case "mode":
|
||||||
|
case "eventActionMode":
|
||||||
|
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY &&(content?.status === commonStatus.wait));
|
||||||
|
default:
|
||||||
|
return modalType === TYPE_MODIFY && (content?.status !== commonStatus.wait);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemGroups = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
row: 0,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'text',
|
||||||
|
key: 'title',
|
||||||
|
label: '스케줄러명',
|
||||||
|
disabled: !isView('name'),
|
||||||
|
width: '300px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 1,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'date',
|
||||||
|
key: 'start_dt',
|
||||||
|
label: '시작일시',
|
||||||
|
disabled: !isView('start_dt'),
|
||||||
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
|
width: '200px',
|
||||||
|
showTime: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 1,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'date',
|
||||||
|
key: 'end_dt',
|
||||||
|
label: '종료일시',
|
||||||
|
disabled: !isView('end_dt'),
|
||||||
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
|
width: '200px',
|
||||||
|
showTime: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 2,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'date',
|
||||||
|
key: 'base_dt',
|
||||||
|
label: '기준일시',
|
||||||
|
disabled: !isView('base_dt'),
|
||||||
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
|
width: '200px',
|
||||||
|
showTime: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 2,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'number',
|
||||||
|
key: 'refresh_interval',
|
||||||
|
label: '새로고침 주기(분)',
|
||||||
|
disabled: !isView('refresh_interval'),
|
||||||
|
width: '100px',
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 3,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'number',
|
||||||
|
key: 'initialization_interval',
|
||||||
|
label: '초기화 주기(분)',
|
||||||
|
disabled: !isView('init_interval'),
|
||||||
|
width: '100px',
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 3,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'number',
|
||||||
|
key: 'snapshot_interval',
|
||||||
|
label: '스냅샷 주기(분)',
|
||||||
|
disabled: !isView('snapshot_interval'),
|
||||||
|
width: '100px',
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
row: 4,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'select',
|
||||||
|
key: 'meta_id',
|
||||||
|
label: '랭킹 모드',
|
||||||
|
disabled: !isView('mode'),
|
||||||
|
width: '150px',
|
||||||
|
options: opRankingMode
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 4,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'select',
|
||||||
|
key: 'event_action_id',
|
||||||
|
label: '이벤트 액션 그룹',
|
||||||
|
disabled: !isView('eventActionMode'),
|
||||||
|
width: '150px',
|
||||||
|
options: opEventActionMode
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal min="760px" $view={detailView}>
|
||||||
|
<Title $align="center">{isView('registry') ? "랭킹 스케줄러 등록" : isView('modify') ? "랭킹 스케줄러 수정" : "랭킹 스케줄러 상세"}</Title>
|
||||||
|
<DetailLayout
|
||||||
|
itemGroups={itemGroups}
|
||||||
|
formData={resultData}
|
||||||
|
onChange={setResultData}
|
||||||
|
disabled={false}
|
||||||
|
columnCount={4}
|
||||||
|
/>
|
||||||
|
{!isView() && isNullValue && <SearchBarAlert $marginTop="25px" $align="right">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
|
||||||
|
<BtnWrapper $gap="10px" $marginTop="10px">
|
||||||
|
<FormStatusBar>
|
||||||
|
<FormStatusLabel>
|
||||||
|
현재상태: {opCommonStatus.find(data => data.value === content?.status)?.name || "등록"}
|
||||||
|
</FormStatusLabel>
|
||||||
|
<FormStatusWarning>
|
||||||
|
{isView('registry') ? '' : t('SCHEDULE_MODAL_STATUS_WARNING')}
|
||||||
|
</FormStatusWarning>
|
||||||
|
</FormStatusBar>
|
||||||
|
<FormButtonContainer $gap="5px">
|
||||||
|
{isView() ?
|
||||||
|
<Button
|
||||||
|
text="확인"
|
||||||
|
name="확인버튼"
|
||||||
|
theme="line"
|
||||||
|
handleClick={() => handleReset()}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
text="취소"
|
||||||
|
theme="line"
|
||||||
|
handleClick={() => showModal('CANCEL_CONFIRM', {
|
||||||
|
type: alertTypes.confirm,
|
||||||
|
onConfirm: () => handleReset()
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
text={isView('modify') ? "수정" : "등록"}
|
||||||
|
name="등록버튼"
|
||||||
|
theme={
|
||||||
|
checkCondition()
|
||||||
|
? 'primary'
|
||||||
|
: 'disable'
|
||||||
|
}
|
||||||
|
handleClick={() => handleSubmit('submit')}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</FormButtonContainer>
|
||||||
|
</BtnWrapper>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initData = {
|
||||||
|
guid: '',
|
||||||
|
title: '',
|
||||||
|
start_dt: '',
|
||||||
|
end_dt: '',
|
||||||
|
base_dt: '',
|
||||||
|
refresh_interval: 60,
|
||||||
|
initialization_interval: 0,
|
||||||
|
snapshot_interval: 1440,
|
||||||
|
meta_id: '',
|
||||||
|
event_action_id: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RankingModal;
|
||||||
|
|
||||||
540
src/components/modal/RewardEventDetailModal.js
Normal file
540
src/components/modal/RewardEventDetailModal.js
Normal file
@@ -0,0 +1,540 @@
|
|||||||
|
import { useState, useEffect, Fragment } from 'react';
|
||||||
|
|
||||||
|
import { Input, Button as AntButton, Select, Alert, Space, Card, Row, Col } from 'antd';
|
||||||
|
import { Title, BtnWrapper } from '../../styles/Components';
|
||||||
|
import Button from '../common/button/Button';
|
||||||
|
import Modal from '../common/modal/Modal';
|
||||||
|
import { EventIsItem, RewardEventModify } from '../../apis';
|
||||||
|
|
||||||
|
import { authList } from '../../store/authList';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { authType, benItems, commonStatus, currencyItemCode } from '../../assets/data';
|
||||||
|
import {
|
||||||
|
DetailRegistInfo, DetailState
|
||||||
|
} from '../../styles/ModuleComponents';
|
||||||
|
import { convertKTC, timeDiffMinute, convertKTCDate } from '../../utils';
|
||||||
|
import { useLoading } from '../../context/LoadingProvider';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
import { DetailLayout } from '../common';
|
||||||
|
|
||||||
|
const RewardEventDetailModal = ({ detailView, handleDetailView, content, setDetailData }) => {
|
||||||
|
const userInfo = useRecoilValue(authList);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
const {withLoading} = useLoading();
|
||||||
|
const {showModal, showToast} = useAlert();
|
||||||
|
|
||||||
|
const id = content && content.id;
|
||||||
|
const updateAuth = userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate);
|
||||||
|
|
||||||
|
const [activeLanguage, setActiveLanguage] = useState('KO');
|
||||||
|
const [item, setItem] = useState('');
|
||||||
|
const [itemCount, setItemCount] = useState(1);
|
||||||
|
const [resource, setResource] = useState('19010001');
|
||||||
|
const [resourceCount, setResourceCount] = useState(1);
|
||||||
|
|
||||||
|
const [resultData, setResultData] = useState({});
|
||||||
|
|
||||||
|
// 과거 판단
|
||||||
|
const [isPast, setIsPast] = useState(false);
|
||||||
|
const [isChanged, setIsChanged] = useState(false);
|
||||||
|
|
||||||
|
const [btnValidation, setBtnValidation] = useState(false);
|
||||||
|
const [isReadOnly, setIsReadOnly] = useState(false);
|
||||||
|
const [itemCheckMsg, setItemCheckMsg] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(content){
|
||||||
|
const start_dt_KTC = convertKTCDate(content.start_dt)
|
||||||
|
const end_dt_KTC = convertKTCDate(content.end_dt)
|
||||||
|
|
||||||
|
setResultData({
|
||||||
|
start_dt: start_dt_KTC,
|
||||||
|
end_dt: end_dt_KTC,
|
||||||
|
event_type: content.event_type,
|
||||||
|
mail_list: content.mail_list,
|
||||||
|
item_list: content.item_list,
|
||||||
|
status: content.status,
|
||||||
|
delete_desc: content.delete_desc
|
||||||
|
});
|
||||||
|
|
||||||
|
start_dt_KTC < (new Date) ? setIsPast(true) : setIsPast(false);
|
||||||
|
content.mail_list.length === 1 && setBtnValidation(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
setItem('');
|
||||||
|
|
||||||
|
}, [content]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(!updateAuth || isPast){
|
||||||
|
setIsReadOnly(true);
|
||||||
|
}else{
|
||||||
|
setIsReadOnly(false);
|
||||||
|
}
|
||||||
|
}, [updateAuth, isPast]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setItemCheckMsg('');
|
||||||
|
}, [item]);
|
||||||
|
|
||||||
|
const getLanguageTabItems = () => {
|
||||||
|
return resultData.mail_list?.map(mail => ({
|
||||||
|
key: mail.language,
|
||||||
|
label: mail.language,
|
||||||
|
children: (
|
||||||
|
<div style={{ padding: '10px', minHeight: '400px', height: 'auto' }}>
|
||||||
|
<Row gutter={[16, 24]}>
|
||||||
|
<Col span={24}>
|
||||||
|
<div>
|
||||||
|
<label style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '8px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: 'rgba(0, 0, 0, 0.85)',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>
|
||||||
|
제목 <span style={{ color: '#ff4d4f' }}>*</span>
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={mail.title || ''}
|
||||||
|
placeholder="우편 제목을 입력하세요"
|
||||||
|
maxLength={30}
|
||||||
|
readOnly={isReadOnly}
|
||||||
|
onChange={(e) => updateMailData(mail.language, 'title', e.target.value.trimStart())}
|
||||||
|
showCount
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<div>
|
||||||
|
<label style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '8px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: 'rgba(0, 0, 0, 0.85)',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>
|
||||||
|
내용 <span style={{ color: '#ff4d4f' }}>*</span>
|
||||||
|
</label>
|
||||||
|
<Input.TextArea
|
||||||
|
value={mail.content || ''}
|
||||||
|
placeholder="우편 내용을 입력하세요"
|
||||||
|
readOnly={isReadOnly}
|
||||||
|
rows={8}
|
||||||
|
maxLength={2000}
|
||||||
|
showCount
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.value.length > 2000) return;
|
||||||
|
updateMailData(mail.language, 'content', e.target.value.trimStart());
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
resize: 'vertical',
|
||||||
|
minHeight: '200px',
|
||||||
|
maxHeight: '400px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
closable: resultData.mail_list?.length > 1 && !isReadOnly, // 마지막 하나가 아니고 읽기전용이 아닐 때만 삭제 가능
|
||||||
|
})) || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateMailData = (language, field, value) => {
|
||||||
|
const updatedMailList = resultData.mail_list.map(mail =>
|
||||||
|
mail.language === language
|
||||||
|
? { ...mail, [field]: value }
|
||||||
|
: mail
|
||||||
|
);
|
||||||
|
setResultData({ ...resultData, mail_list: updatedMailList });
|
||||||
|
setIsChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabClose = (targetKey) => {
|
||||||
|
if (resultData.mail_list.length <= 1) return;
|
||||||
|
|
||||||
|
const filterList = resultData.mail_list.filter(el => el.language !== targetKey);
|
||||||
|
setResultData({ ...resultData, mail_list: filterList });
|
||||||
|
|
||||||
|
// 삭제된 탭이 현재 활성 탭이었다면 첫 번째 탭으로 변경
|
||||||
|
if (activeLanguage === targetKey) {
|
||||||
|
setActiveLanguage(filterList[0]?.language || 'KO');
|
||||||
|
}
|
||||||
|
setIsChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 아이템 추가
|
||||||
|
const handleItemList = async () => {
|
||||||
|
if(benItems.includes(item)){
|
||||||
|
showToast('MAIL_ITEM_ADD_BEN', {type: alertTypes.warning});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(item.length === 0 || itemCount.length === 0) return;
|
||||||
|
|
||||||
|
const result = await EventIsItem(token, {item: item});
|
||||||
|
|
||||||
|
if(result.data.result === "ERROR"){
|
||||||
|
setItemCheckMsg(t('NOT_ITEM'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemIndex = resultData.item_list.findIndex((data) => data.item === item);
|
||||||
|
if (itemIndex !== -1) {
|
||||||
|
setItemCheckMsg(t('MAIL_ITEM_ADD_DUPL'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newItem = { item: item, item_cnt: itemCount, item_name: result.data.data.item_info.item_name };
|
||||||
|
|
||||||
|
resultData.item_list.push(newItem);
|
||||||
|
|
||||||
|
setIsChanged(true);
|
||||||
|
setItem('');
|
||||||
|
setItemCount('');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 아이템 삭제
|
||||||
|
const onItemRemove = id => {
|
||||||
|
let filterList = resultData.item_list && resultData.item_list.filter(item => item !== resultData.item_list[id]);
|
||||||
|
setIsChanged(true);
|
||||||
|
|
||||||
|
setResultData({ ...resultData, item_list: filterList });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 자원 추가
|
||||||
|
const handleResourceList = (e) => {
|
||||||
|
if(resource.length === 0 || resourceCount.length === 0) return;
|
||||||
|
|
||||||
|
const itemIndex = resultData.item_list.findIndex(
|
||||||
|
(item) => item.item === resource
|
||||||
|
);
|
||||||
|
|
||||||
|
if (itemIndex !== -1) {
|
||||||
|
const item_cnt = resultData.item_list[itemIndex].item_cnt;
|
||||||
|
resultData.item_list[itemIndex].item_cnt = Number(item_cnt) + Number(resourceCount);
|
||||||
|
} else {
|
||||||
|
const name = currencyItemCode.find(well => well.value === resource).name;
|
||||||
|
const newItem = { item: resource, item_cnt: resourceCount, item_name: name };
|
||||||
|
resultData.item_list.push(newItem);
|
||||||
|
}
|
||||||
|
setIsChanged(true);
|
||||||
|
setResource('')
|
||||||
|
setResourceCount('');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 확인 버튼 후 다 초기화
|
||||||
|
const handleReset = () => {
|
||||||
|
setBtnValidation(false);
|
||||||
|
setIsChanged(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const conditionCheck = () => {
|
||||||
|
return (
|
||||||
|
content && content.mail_list.every(data => data.content !== '' && data.title !== '') &&
|
||||||
|
isChanged
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (type, param = null) => {
|
||||||
|
switch (type) {
|
||||||
|
case "submit":
|
||||||
|
if (!conditionCheck()) return;
|
||||||
|
|
||||||
|
showModal('MAIL_UPDATE_SAVE', {
|
||||||
|
type: alertTypes.confirm,
|
||||||
|
onConfirm: () => handleSubmit('updateConfirm')
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "updateConfirm":
|
||||||
|
const timeDiff = timeDiffMinute(resultData.start_dt, (new Date))
|
||||||
|
// 이벤트 시작 30분전이나 이미 SystemMail이 add된 상태에서는 수정할 수 없다.
|
||||||
|
if(content.add_flag || timeDiff <= 30){
|
||||||
|
showToast('EVENT_TIME_LIMIT_UPDATE', {type: alertTypes.warning});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
withLoading( async () => {
|
||||||
|
return await RewardEventModify(token, id, resultData);
|
||||||
|
}).catch(error => {
|
||||||
|
showToast('API_FAIL', {type: alertTypes.error});
|
||||||
|
}).finally(() => {
|
||||||
|
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
|
||||||
|
handleDetailView();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailState = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case commonStatus.wait:
|
||||||
|
return <DetailState>대기</DetailState>;
|
||||||
|
case commonStatus.running:
|
||||||
|
return <DetailState>진행중</DetailState>;
|
||||||
|
case commonStatus.finish:
|
||||||
|
return <DetailState result={commonStatus.finish}>완료</DetailState>;
|
||||||
|
case commonStatus.fail:
|
||||||
|
return <DetailState result={commonStatus.fail}>실패</DetailState>;
|
||||||
|
case commonStatus.delete:
|
||||||
|
return <DetailState result={commonStatus.delete}>삭제</DetailState>;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 아이템 목록 렌더링 컴포넌트
|
||||||
|
const renderItemList = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{resultData.item_list && resultData.item_list.length > 0 && (
|
||||||
|
<Space wrap>
|
||||||
|
{resultData.item_list.map((data, index) => (
|
||||||
|
<Card
|
||||||
|
key={index}
|
||||||
|
title={data.item_name}
|
||||||
|
size="small"
|
||||||
|
extra={
|
||||||
|
!isReadOnly && (
|
||||||
|
<AntButton
|
||||||
|
type="text"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
onClick={() => onItemRemove(index)}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</AntButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
style={{ minWidth: '150px' }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div>{data.item}</div>
|
||||||
|
<div>수량: {data.item_cnt}</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 아이템 추가 컴포넌트
|
||||||
|
const renderItemAdd = () => {
|
||||||
|
return (
|
||||||
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
|
<Input
|
||||||
|
placeholder="Item Meta id 입력"
|
||||||
|
value={item}
|
||||||
|
onChange={(e) => setItem(e.target.value.trimStart())}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
style={{ width: '200px' }}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="수량"
|
||||||
|
value={itemCount}
|
||||||
|
onChange={(e) => setItemCount(e.target.value)}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
style={{ width: '120px' }}
|
||||||
|
min={1}
|
||||||
|
/>
|
||||||
|
<AntButton
|
||||||
|
type="primary"
|
||||||
|
onClick={handleItemList}
|
||||||
|
disabled={itemCount.length === 0 || item.length === 0 || isReadOnly}
|
||||||
|
>
|
||||||
|
추가
|
||||||
|
</AntButton>
|
||||||
|
</Space.Compact>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 자원 추가 컴포넌트
|
||||||
|
const renderResourceAdd = () => {
|
||||||
|
return (
|
||||||
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
|
<Select
|
||||||
|
value={resource}
|
||||||
|
onChange={setResource}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
style={{ width: '200px' }}
|
||||||
|
placeholder="자원 선택"
|
||||||
|
>
|
||||||
|
{currencyItemCode.map((data, index) => (
|
||||||
|
<Select.Option key={index} value={data.value}>
|
||||||
|
{data.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="수량"
|
||||||
|
value={resourceCount}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
onChange={(e) => setResourceCount(e.target.value)}
|
||||||
|
style={{ width: '120px' }}
|
||||||
|
min={1}
|
||||||
|
/>
|
||||||
|
<AntButton
|
||||||
|
type="primary"
|
||||||
|
onClick={handleResourceList}
|
||||||
|
disabled={resourceCount.length === 0 || resource.length === 0 || isReadOnly}
|
||||||
|
>
|
||||||
|
추가
|
||||||
|
</AntButton>
|
||||||
|
</Space.Compact>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemGroups = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
row: 0,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'dateRange',
|
||||||
|
keys: {
|
||||||
|
start: 'start_dt',
|
||||||
|
end: 'end_dt'
|
||||||
|
},
|
||||||
|
label: '이벤트 기간',
|
||||||
|
disabled: isReadOnly,
|
||||||
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
|
showTime: true,
|
||||||
|
startLabel: '시작 일시',
|
||||||
|
endLabel: '종료 일시'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 0,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 1,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'status',
|
||||||
|
label: '이벤트 상태',
|
||||||
|
render: () => detailState(resultData.status)
|
||||||
|
},
|
||||||
|
...(resultData.status === commonStatus.delete ? [{
|
||||||
|
row: 0,
|
||||||
|
col: 3,
|
||||||
|
colSpan: 1,
|
||||||
|
type: 'display',
|
||||||
|
key: 'delete_desc',
|
||||||
|
label: '삭제 사유',
|
||||||
|
value: resultData.delete_desc || ''
|
||||||
|
}] : [{
|
||||||
|
row: 0,
|
||||||
|
col: 3,
|
||||||
|
colSpan: 1,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'empty_space',
|
||||||
|
label: '',
|
||||||
|
render: () => <div></div>
|
||||||
|
}]),
|
||||||
|
{
|
||||||
|
row: 1,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'tab',
|
||||||
|
key: 'language_tabs',
|
||||||
|
tabItems: getLanguageTabItems(),
|
||||||
|
activeKey: activeLanguage,
|
||||||
|
onTabChange: setActiveLanguage,
|
||||||
|
onTabClose: handleTabClose
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 2,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'item_add',
|
||||||
|
label: '아이템 추가',
|
||||||
|
render: renderItemAdd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 3,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'resource_add',
|
||||||
|
label: '자원 추가',
|
||||||
|
render: renderResourceAdd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 4,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'custom',
|
||||||
|
key: 'item_list',
|
||||||
|
render: renderItemList
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal min="960px" $view={detailView}>
|
||||||
|
<Title $align="center">이벤트 상세 정보</Title>
|
||||||
|
{content &&
|
||||||
|
<DetailRegistInfo>
|
||||||
|
<span>등록자 : {content.create_by}</span>
|
||||||
|
<span>등록일 : {convertKTC(content.create_dt, false)}</span>
|
||||||
|
{typeof content.update_by !== 'undefined' && (
|
||||||
|
<>
|
||||||
|
<span>수정자 : {content.update_by}</span>
|
||||||
|
<span>수정일 : {convertKTC(content.update_dt, false)}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DetailRegistInfo>
|
||||||
|
}
|
||||||
|
|
||||||
|
<DetailLayout
|
||||||
|
itemGroups={itemGroups}
|
||||||
|
formData={resultData}
|
||||||
|
onChange={setResultData}
|
||||||
|
disabled={false}
|
||||||
|
columnCount={4}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{itemCheckMsg && (
|
||||||
|
<Alert
|
||||||
|
message={itemCheckMsg}
|
||||||
|
type="error"
|
||||||
|
style={{ marginTop: '8px', width: '300px' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<BtnWrapper $justify="flex-end" $gap="10px" $paddingTop="20px">
|
||||||
|
<Button
|
||||||
|
text="확인"
|
||||||
|
theme="line"
|
||||||
|
name="확인버튼"
|
||||||
|
handleClick={() => {
|
||||||
|
handleDetailView();
|
||||||
|
handleReset();
|
||||||
|
setDetailData('');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!isReadOnly && (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
text="수정"
|
||||||
|
id="수정버튼"
|
||||||
|
theme={conditionCheck() ? 'primary' : 'disable'}
|
||||||
|
handleClick={() => handleSubmit('submit')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</BtnWrapper>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RewardEventDetailModal;
|
||||||
|
|
||||||
|
|
||||||
150
src/components/searchBar/AssetsIndexSearchBar.js
Normal file
150
src/components/searchBar/AssetsIndexSearchBar.js
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { InputLabel, TextInput } from '../../styles/Components';
|
||||||
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
import { AssetsIndexView, CurrencyAcquireIndexView, ItemIndexView } from '../../apis';
|
||||||
|
|
||||||
|
export const useAssetsIndexSearch = (token, initialPageSize) => {
|
||||||
|
const {showToast} = useAlert();
|
||||||
|
|
||||||
|
const [searchParams, setSearchParams] = useState({
|
||||||
|
start_dt: (() => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate() - 1);
|
||||||
|
return date;
|
||||||
|
})(),
|
||||||
|
end_dt: (() => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate());
|
||||||
|
return date;
|
||||||
|
})(),
|
||||||
|
order_by: 'ASC',
|
||||||
|
page_size: initialPageSize,
|
||||||
|
page_no: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [data, setData] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initialLoad = async () => {
|
||||||
|
await fetchData(searchParams);
|
||||||
|
};
|
||||||
|
|
||||||
|
initialLoad();
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
const fetchData = useCallback(async (params) => {
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const result = await AssetsIndexView(
|
||||||
|
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);
|
||||||
|
return result;
|
||||||
|
} 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, updateSearchParams, fetchData]);
|
||||||
|
|
||||||
|
const handleReset = useCallback(async () => {
|
||||||
|
const now = new Date();
|
||||||
|
now.setDate(now.getDate() - 1);
|
||||||
|
const resetParams = {
|
||||||
|
start_dt: now,
|
||||||
|
end_dt: new Date(),
|
||||||
|
order_by: 'ASC',
|
||||||
|
page_size: initialPageSize,
|
||||||
|
page_no: 1
|
||||||
|
};
|
||||||
|
setSearchParams(resetParams);
|
||||||
|
return await fetchData(resetParams);
|
||||||
|
}, [initialPageSize, fetchData]);
|
||||||
|
|
||||||
|
const handlePageChange = useCallback(async (newPage) => {
|
||||||
|
return await handleSearch({ page_no: newPage }, true);
|
||||||
|
}, [handleSearch]);
|
||||||
|
|
||||||
|
const handlePageSizeChange = useCallback(async (newSize) => {
|
||||||
|
return await handleSearch({ page_size: newSize, page_no: 1 }, true);
|
||||||
|
}, [handleSearch]);
|
||||||
|
|
||||||
|
const handleOrderByChange = useCallback(async (newOrder) => {
|
||||||
|
return await handleSearch({ order_by: newOrder }, true);
|
||||||
|
}, [handleSearch]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchParams,
|
||||||
|
loading,
|
||||||
|
data,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
handlePageChange,
|
||||||
|
handlePageSizeChange,
|
||||||
|
handleOrderByChange,
|
||||||
|
updateSearchParams
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const AssetsIndexSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||||
|
const handleSubmit = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
onSearch(searchParams, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchList = [
|
||||||
|
<>
|
||||||
|
<InputLabel>일자</InputLabel>
|
||||||
|
<SearchPeriod
|
||||||
|
startDate={searchParams.start_dt}
|
||||||
|
handleStartDate={date => onSearch({ start_dt: date }, false)}
|
||||||
|
endDate={searchParams.end_dt}
|
||||||
|
handleEndDate={date => onSearch({ end_dt: date }, false)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
];
|
||||||
|
|
||||||
|
return <SearchBarLayout firstColumnData={searchList} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AssetsIndexSearchBar;
|
||||||
@@ -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,11 +1,15 @@
|
|||||||
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||||
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { logAction, logDomain, userSearchType2 } from '../../assets/data/options';
|
import { logAction, logDomain, userSearchType2 } from '../../assets/data/options';
|
||||||
import { BusinessLogList } from '../../apis/Log';
|
import { BusinessLogList } from '../../apis/Log';
|
||||||
import {SearchFilter} from '../ServiceManage';
|
import {SearchFilter} from '../ServiceManage';
|
||||||
import { useAlert } from '../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import { alertTypes } from '../../assets/data/types';
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { DatePicker } from 'antd';
|
||||||
|
import DateRangePicker from '../common/Date/DateRangePicker';
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
export const useBusinessLogSearch = (token, initialPageSize) => {
|
export const useBusinessLogSearch = (token, initialPageSize) => {
|
||||||
const {showToast} = useAlert();
|
const {showToast} = useAlert();
|
||||||
@@ -16,16 +20,8 @@ export const useBusinessLogSearch = (token, initialPageSize) => {
|
|||||||
log_action: 'None',
|
log_action: 'None',
|
||||||
log_domain: 'BASE',
|
log_domain: 'BASE',
|
||||||
tran_id: '',
|
tran_id: '',
|
||||||
start_dt: (() => {
|
start_dt: dayjs().subtract(1, 'day').startOf('day'),
|
||||||
const date = new Date();
|
end_dt: dayjs().subtract(1, 'day').endOf('day'),
|
||||||
date.setDate(date.getDate() - 1);
|
|
||||||
return date;
|
|
||||||
})(),
|
|
||||||
end_dt: (() => {
|
|
||||||
const date = new Date();
|
|
||||||
date.setDate(date.getDate() - 1);
|
|
||||||
return date;
|
|
||||||
})(),
|
|
||||||
filters: [],
|
filters: [],
|
||||||
order_by: 'ASC',
|
order_by: 'ASC',
|
||||||
page_size: initialPageSize,
|
page_size: initialPageSize,
|
||||||
@@ -92,16 +88,14 @@ export const useBusinessLogSearch = (token, initialPageSize) => {
|
|||||||
}, [searchParams, fetchData]);
|
}, [searchParams, fetchData]);
|
||||||
|
|
||||||
const handleReset = useCallback(async () => {
|
const handleReset = useCallback(async () => {
|
||||||
const now = new Date();
|
|
||||||
now.setDate(now.getDate() - 1);
|
|
||||||
const resetParams = {
|
const resetParams = {
|
||||||
search_type: 'GUID',
|
search_type: 'GUID',
|
||||||
search_data: '',
|
search_data: '',
|
||||||
log_action: 'None',
|
log_action: 'None',
|
||||||
log_domain: 'BASE',
|
log_domain: 'BASE',
|
||||||
tran_id: '',
|
tran_id: '',
|
||||||
start_dt: now,
|
start_dt: dayjs().subtract(1, 'day').startOf('day'),
|
||||||
end_dt: now,
|
end_dt: dayjs().subtract(1, 'day').endOf('day'),
|
||||||
filters: [],
|
filters: [],
|
||||||
order_by: 'ASC',
|
order_by: 'ASC',
|
||||||
page_size: initialPageSize,
|
page_size: initialPageSize,
|
||||||
@@ -197,11 +191,11 @@ const BusinessLogSearchBar = ({ searchParams, onSearch, onReset }) => {
|
|||||||
</>,
|
</>,
|
||||||
<>
|
<>
|
||||||
<InputLabel>일자</InputLabel>
|
<InputLabel>일자</InputLabel>
|
||||||
<SearchPeriod
|
<DateRangePicker
|
||||||
startDate={searchParams.start_dt}
|
value={[searchParams.start_dt, searchParams.end_dt]}
|
||||||
handleStartDate={date => onSearch({ start_dt: date }, false)}
|
onChange={(dates) => {
|
||||||
endDate={searchParams.end_dt}
|
onSearch({ start_dt: dates[0], end_dt: dates[1] }, false);
|
||||||
handleEndDate={date => onSearch({ end_dt: date }, false)}
|
}}
|
||||||
/>
|
/>
|
||||||
</>,
|
</>,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
|||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { getOptionsArray } from '../../utils';
|
import { getOptionsArray } from '../../utils';
|
||||||
import { PageSkeleton } from '../Skeleton/SearchSkeleton';
|
import { PageSkeleton } from '../Skeleton/SearchSkeleton';
|
||||||
|
import { DateRangePicker } from '../common';
|
||||||
|
|
||||||
const renderSearchField = (field, searchParams, onSearch) => {
|
const renderSearchField = (field, searchParams, onSearch) => {
|
||||||
const { type, id, label, placeholder, width, optionsRef } = field;
|
const { type, id, label, placeholder, width, optionsRef, format, showTime } = field;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'text':
|
case 'text':
|
||||||
@@ -47,14 +48,45 @@ const renderSearchField = (field, searchParams, onSearch) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
case 'period':
|
case 'period':
|
||||||
|
const startDateValue = searchParams[field.startDateId];
|
||||||
|
const endDateValue = searchParams[field.endDateId];
|
||||||
|
|
||||||
|
// 날짜 값이 있을 때만 배열로 변환
|
||||||
|
const rangeValue = (startDateValue && endDateValue) ?
|
||||||
|
[startDateValue, endDateValue] :
|
||||||
|
null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{label && label !== 'undefined' && <InputLabel $require={field.required}>{label}</InputLabel>}
|
{label && label !== 'undefined' && <InputLabel $require={field.required}>{label}</InputLabel>}
|
||||||
<SearchPeriod
|
<DateRangePicker
|
||||||
startDate={searchParams[field.startDateId]}
|
value={rangeValue}
|
||||||
handleStartDate={date => onSearch({ [field.startDateId]: date }, false)}
|
onChange={(dates) => {
|
||||||
endDate={searchParams[field.endDateId]}
|
if (dates && dates.length === 2) {
|
||||||
handleEndDate={date => onSearch({ [field.endDateId]: date }, false)}
|
let startDate = dates[0];
|
||||||
|
let endDate = dates[1];
|
||||||
|
if(!showTime) {
|
||||||
|
// 시작 날짜는 00:00:00으로 설정
|
||||||
|
startDate = startDate.startOf('day');
|
||||||
|
// 종료 날짜는 23:59:59로 설정
|
||||||
|
endDate = endDate.endOf('day');
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearch({
|
||||||
|
[field.startDateId]: startDate.format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
[field.endDateId]: endDate.format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}, false);
|
||||||
|
} else {
|
||||||
|
onSearch({
|
||||||
|
[field.startDateId]: '',
|
||||||
|
[field.endDateId]: ''
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
showTime={showTime ||false}
|
||||||
|
format={format || "YYYY-MM-DD"}
|
||||||
|
placeholder={['시작일', '종료일']}
|
||||||
|
style={{ width: width || '280px' }}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
165
src/components/searchBar/CurrencyAcquireIndexSearchBar.js
Normal file
165
src/components/searchBar/CurrencyAcquireIndexSearchBar.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import { InputLabel, SelectInput } from '../../styles/Components';
|
||||||
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
import { CurrencyType } from '../../assets/data';
|
||||||
|
import { CurrencyAcquireIndexView } from '../../apis';
|
||||||
|
|
||||||
|
export const useCurrencyAcquireIndexSearch = (token, initialPageSize) => {
|
||||||
|
const {showToast} = useAlert();
|
||||||
|
|
||||||
|
const [searchParams, setSearchParams] = useState({
|
||||||
|
start_dt: (() => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate() - 1);
|
||||||
|
return date;
|
||||||
|
})(),
|
||||||
|
end_dt: (() => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate());
|
||||||
|
return date;
|
||||||
|
})(),
|
||||||
|
currency_type: 'Gold',
|
||||||
|
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 CurrencyAcquireIndexView(
|
||||||
|
token,
|
||||||
|
params.start_dt.toISOString(),
|
||||||
|
params.end_dt.toISOString(),
|
||||||
|
params.currency_type,
|
||||||
|
"Acquire",
|
||||||
|
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);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
showToast('error', {type: alertTypes.error});
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
const updateSearchParams = useCallback((newParams) => {
|
||||||
|
setSearchParams(prev => ({
|
||||||
|
...prev,
|
||||||
|
...newParams
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSearch = useCallback(async (newParams = {}, executeSearch = true) => {
|
||||||
|
const updatedParams = {
|
||||||
|
...searchParams,
|
||||||
|
...newParams,
|
||||||
|
page_no: newParams.page_no || 1 // Reset to first page on new search
|
||||||
|
};
|
||||||
|
updateSearchParams(updatedParams);
|
||||||
|
|
||||||
|
if (executeSearch) {
|
||||||
|
return await fetchData(updatedParams);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [searchParams, fetchData]);
|
||||||
|
|
||||||
|
const handleReset = useCallback(async () => {
|
||||||
|
const now = new Date();
|
||||||
|
now.setDate(now.getDate() - 1);
|
||||||
|
const resetParams = {
|
||||||
|
start_dt: now,
|
||||||
|
end_dt: new Date(),
|
||||||
|
currency_type: 'Gold',
|
||||||
|
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 CurrencyAcquireIndexSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||||
|
const handleSubmit = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
onSearch(searchParams, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchList = [
|
||||||
|
<>
|
||||||
|
<InputLabel>일자</InputLabel>
|
||||||
|
<SearchPeriod
|
||||||
|
startDate={searchParams.start_dt}
|
||||||
|
handleStartDate={date => onSearch({ start_dt: date }, false)}
|
||||||
|
endDate={searchParams.end_dt}
|
||||||
|
handleEndDate={date => onSearch({ end_dt: date }, false)}
|
||||||
|
/>
|
||||||
|
</>,
|
||||||
|
<>
|
||||||
|
<InputLabel>재화종류</InputLabel>
|
||||||
|
<SelectInput value={searchParams.currency_type} onChange={e => onSearch({ currency_type: e.target.value }, false)} >
|
||||||
|
{CurrencyType.map((data, index) => (
|
||||||
|
<option key={index} value={data.value}>
|
||||||
|
{data.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</SelectInput>
|
||||||
|
</>,
|
||||||
|
];
|
||||||
|
|
||||||
|
return <SearchBarLayout firstColumnData={searchList} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CurrencyAcquireIndexSearchBar;
|
||||||
165
src/components/searchBar/CurrencyConsumeIndexSearchBar.js
Normal file
165
src/components/searchBar/CurrencyConsumeIndexSearchBar.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import { InputLabel, SelectInput } from '../../styles/Components';
|
||||||
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
import { CurrencyType } from '../../assets/data';
|
||||||
|
import { CurrencyAcquireIndexView } from '../../apis';
|
||||||
|
|
||||||
|
export const useCurrencyConsumeIndexSearch = (token, initialPageSize) => {
|
||||||
|
const {showToast} = useAlert();
|
||||||
|
|
||||||
|
const [searchParams, setSearchParams] = useState({
|
||||||
|
start_dt: (() => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate() - 1);
|
||||||
|
return date;
|
||||||
|
})(),
|
||||||
|
end_dt: (() => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate());
|
||||||
|
return date;
|
||||||
|
})(),
|
||||||
|
currency_type: 'Gold',
|
||||||
|
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 CurrencyAcquireIndexView(
|
||||||
|
token,
|
||||||
|
params.start_dt.toISOString(),
|
||||||
|
params.end_dt.toISOString(),
|
||||||
|
params.currency_type,
|
||||||
|
"Consume",
|
||||||
|
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);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
showToast('error', {type: alertTypes.error});
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
const updateSearchParams = useCallback((newParams) => {
|
||||||
|
setSearchParams(prev => ({
|
||||||
|
...prev,
|
||||||
|
...newParams
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSearch = useCallback(async (newParams = {}, executeSearch = true) => {
|
||||||
|
const updatedParams = {
|
||||||
|
...searchParams,
|
||||||
|
...newParams,
|
||||||
|
page_no: newParams.page_no || 1 // Reset to first page on new search
|
||||||
|
};
|
||||||
|
updateSearchParams(updatedParams);
|
||||||
|
|
||||||
|
if (executeSearch) {
|
||||||
|
return await fetchData(updatedParams);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [searchParams, fetchData]);
|
||||||
|
|
||||||
|
const handleReset = useCallback(async () => {
|
||||||
|
const now = new Date();
|
||||||
|
now.setDate(now.getDate() - 1);
|
||||||
|
const resetParams = {
|
||||||
|
start_dt: now,
|
||||||
|
end_dt: new Date(),
|
||||||
|
currency_type: 'Gold',
|
||||||
|
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 CurrencyConsumeIndexSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||||
|
const handleSubmit = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
onSearch(searchParams, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchList = [
|
||||||
|
<>
|
||||||
|
<InputLabel>일자</InputLabel>
|
||||||
|
<SearchPeriod
|
||||||
|
startDate={searchParams.start_dt}
|
||||||
|
handleStartDate={date => onSearch({ start_dt: date }, false)}
|
||||||
|
endDate={searchParams.end_dt}
|
||||||
|
handleEndDate={date => onSearch({ end_dt: date }, false)}
|
||||||
|
/>
|
||||||
|
</>,
|
||||||
|
<>
|
||||||
|
<InputLabel>재화종류</InputLabel>
|
||||||
|
<SelectInput value={searchParams.currency_type} onChange={e => onSearch({ currency_type: e.target.value }, false)} >
|
||||||
|
{CurrencyType.map((data, index) => (
|
||||||
|
<option key={index} value={data.value}>
|
||||||
|
{data.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</SelectInput>
|
||||||
|
</>,
|
||||||
|
];
|
||||||
|
|
||||||
|
return <SearchBarLayout firstColumnData={searchList} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CurrencyConsumeIndexSearchBar;
|
||||||
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;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user