Compare commits

..

74 Commits

Author SHA1 Message Date
b801552839 제작 아이템 조회
랭킹 점수 관리
2025-09-16 16:43:58 +09:00
3169055646 칼리움 요청 날짜처리 변경
랭킹 스케줄 추가
2025-09-15 16:40:04 +09:00
5d2e1918d1 비즈니스로그조회 시간 추가
메타데이터 로드 추가
아이템 백과사전 추가
칼리움 요청 날짜처리 변경
2025-09-15 16:39:15 +09:00
4407fdc6b6 컴포넌트 관련 변경
이미지 업로드 한글명칭 불가 처리
유저 조회 부분 수정
2025-09-15 16:37:46 +09:00
b01c5cd410 아이템 조회 아이템ID 조건 추가 2025-09-15 16:25:12 +09:00
63b3704e89 히스토리 조회 관련 수정 2025-09-15 16:24:41 +09:00
f78a4912a6 event > rewardEvent 변경
월드이벤트(event) 추가
2025-09-15 16:23:48 +09:00
e25bcdc86e 선택 드랍다운 넓이 수정
아이템 백과사전 추가
2025-09-04 10:37:50 +09:00
5143b45610 모달 스크롤 추가
detailGrid 수정
전투이벤트, 랜드경매, 이벤트, 메일 상세 수정
랜드경매 예약종료일 제거
2025-08-09 09:50:14 +09:00
f4b629df52 경제지표 재화 보유
경제지표 아이템 보유
게임로그 스냅샷
히스토리 비즈니스로그 기준 변경
2025-08-04 17:40:37 +09:00
2ba8594e6b 칼리움 완료처리 파라미터 변경
우편 상세 확인시 페이지 처리
조회조건 변경시 페이지 초기화
랜드경매 메시지 제거
2025-07-28 14:13:01 +09:00
d3470e3d03 nginx 파일 제한 증가 2025-07-21 16:33:02 +09:00
99943c0b19 퀘스트 강제 완료
경제지표 재화 헤더 스타일 변경
2025-07-18 15:18:45 +09:00
26114c9a9b 코드 정리 2025-07-17 14:40:07 +09:00
952701f68b 게임로그 유저생성 로그 조회
게임로그 유저로그인 로그 조회
2025-07-17 14:38:07 +09:00
7041d4a649 유저 지표 잔존율 생성 2025-07-16 18:39:30 +09:00
7fa9abcad4 탭 모션 적용 2025-07-16 18:38:38 +09:00
943b146496 마이홈 리스트형식으로 변경 2025-07-14 13:53:14 +09:00
88585c1b24 전투이벤트 최대 진행시간 예외처리 2025-07-13 11:29:31 +09:00
991462c0d7 게임로그 아이템
게임로그 재화(아이템) 추가
2025-07-13 11:29:04 +09:00
bab594918e datepicker 옵션 변경 2025-07-07 14:26:11 +09:00
c4099c0cf0 메뉴 앤트디자인 메뉴로 교체
헤더 Breadcrumb 추가, profile 수정
2025-07-07 14:25:02 +09:00
0d8fb7b327 전투이벤트 진행시간 추가
진행시간 기준 종료시간 계산
2025-07-01 18:05:59 +09:00
d4db33bcf0 detailGrid 탭 추가 2025-07-01 14:41:07 +09:00
38dac99278 비즈니스로그 타입 예외처리 2025-07-01 14:40:41 +09:00
28094e1c48 배너 detailGrid 적용
배너 수정 및 삭제
2025-07-01 14:40:13 +09:00
67c048a11d 메뉴배너 detailGrid 적용 2025-06-27 09:28:13 +09:00
f6a0319701 메뉴배너 detailGrid 적용 2025-06-27 09:28:06 +09:00
0368bf77e7 antBUtton 생성
topButton 이미지 변경
엑셀 버튼 조정
tab 컨트론 생성
detailGrid, layout 생성
modal motion 적용
2025-06-27 09:25:41 +09:00
b2b579ead1 전투시스템 스케줄러 game mode 방식 변경 2025-06-19 18:54:15 +09:00
495243a1a3 경제지표 재화 추가
게임 로그 재화지표 export api 추가
경제지표 재화 상세 > 재화 로그 페이지 이동 처리
2025-06-16 15:45:36 +09:00
7993f37e26 qa 주소 변경 2025-06-16 15:43:20 +09:00
93a7c087e1 style, utils 수정 2025-06-12 14:16:46 +09:00
38fa323db6 Log currency 관련 API 호출 추가
components 정리에 따른 호출 위치 수정
excelExportButton에 functionName 추가
게임 로그조회 수정
2025-06-12 14:16:26 +09:00
6f9f0307ac component 정리
currencyLogSearchBar 생성
currencyLogCOntent 생성
excelExportButton api호출 방식 수정
2025-06-12 14:08:11 +09:00
dc7934d906 비즈니스 로그 조회 및 파일 다운 수정 2025-06-04 15:19:08 +09:00
9d06246aba 칼리움 요청 실패 표시 및 클릭시 페이지 이동 처리 2025-05-23 15:58:04 +09:00
1532793cc1 LogView 리팩토링
LogDetailModal 생성
화면별 히스토리(LogDetailModal) 적용
2025-05-22 14:55:37 +09:00
64bf449de7 이용자 제재 해지 사유 입력 2025-05-15 17:50:01 +09:00
9be5bb388a data 정보 수정
우편 내용 복사 기능
우편 코드 정리
유저 인벤토리 아이템 삭제 제거
2025-05-15 17:49:45 +09:00
d219a128bc api result 부분 수정 2025-05-15 17:43:20 +09:00
ce2f3db35c 닉네임 변경 처리 수정 2025-05-12 17:06:11 +09:00
b5efab7755 조회조건 필터 랜더링에 이슈로 컴포넌트 분리작업 2025-05-12 10:46:13 +09:00
1cc20b65e2 이용자제재 수정 2025-05-12 10:44:35 +09:00
0b85232969 전투이벤트 등록 알림 비동기에따른 state 초기화 부분 수정 2025-05-12 10:43:57 +09:00
4291d7976c 이용자제재 엑셀 업로드 처리 수정
이용자제재 상세 표시 부분 수정
2025-05-12 10:43:24 +09:00
065c081a85 조회조건 스켈레톤 2025-05-01 07:04:36 +09:00
fa290b64ec api 공통 모듈생성
search, api 공통 모듈 생성
공통모듈 화면 별 반영
2025-05-01 07:04:14 +09:00
f8d5b2197d 안쓰는부분 삭제 2025-05-01 07:02:25 +09:00
3e5c3f0167 ai 연동 2025-04-25 15:56:56 +09:00
826459f304 toast 메시지 추가
alert 글로벌화
loading 글로벌화
2025-04-25 15:33:21 +09:00
d2ac5b338e 메뉴 배너 관리 2025-04-21 14:14:34 +09:00
1598fa93b6 비즈니스 로그 조회조건 수정시 자동 조회 안되게 수정 2025-04-14 12:15:29 +09:00
24eb59d937 비즈니스 로그 수정 2025-04-09 15:45:09 +09:00
a74376108a 조회조건 레이아웃 수정
엑셀다운버튼 수정
progress 추가
front pagenation 추가
2025-04-09 15:44:51 +09:00
80a3c0ab8a 조회조건 레이아웃 수정 2025-04-08 17:30:07 +09:00
f290f0dbf0 dynamodb pagination 추가
유저조회 우편 페이징 처리
hook 수정
2025-04-03 15:48:55 +09:00
9221a06a8e 기타 수정 2025-04-02 18:02:36 +09:00
2c693b2503 유저 조회 접속 상태 표시
유저 조회 kick 처리
2025-04-02 18:02:11 +09:00
73f8448b24 기타 수정 2025-03-26 15:31:21 +09:00
ffdfad1223 데이터 초기화 화면 추가 2025-03-26 15:31:15 +09:00
8bd7e8325d 권한 수정 사용자 권한레벨별 처리 수정 2025-03-26 15:30:40 +09:00
daf4c62aa7 데이터 정보 수정 2025-03-26 15:30:07 +09:00
a333f55f81 비즈니스 로그 위치 변경 2025-03-26 15:29:54 +09:00
894eb17fd8 유저 권한레벨 처리 2025-03-26 15:28:26 +09:00
cddd8e6333 탑 버튼 추가 2025-03-26 15:27:24 +09:00
a0087a1e29 비즈니스 로그조회 추가 2025-03-19 10:56:57 +09:00
f2d7c87f38 랜드 소유권 변경 타입추가 및 버그 수정 2025-03-14 18:24:05 +09:00
5d9b6871fb 랜드 소유권 변경 예약 처리 및 취소 처리 2025-03-13 14:45:31 +09:00
3efd663f0d 랜드 소유권 변경 2025-03-07 18:32:20 +09:00
04adce4aaf 랜드 정보 조회 2025-03-06 12:04:51 +09:00
2330bbc393 조회조건 value 버그 수정 2025-02-26 22:29:52 +09:00
2b2cec3d10 랜드 정보 조회 - 조회 부분 작업 2025-02-26 22:29:21 +09:00
24e09a65bc 첫 조회 동안 계속 useEffect가 도는현상으로 수정 2025-02-26 22:27:59 +09:00
261 changed files with 28343 additions and 10590 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
@@ -11,11 +15,16 @@ server {
# api reverse proxy # api reverse proxy
location /api/ { location /api/ {
proxy_pass http://172.40.129.180:23450; proxy_pass http://172.24.128.231:23450;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
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;

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -3,13 +3,17 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.6.1",
"@hookform/resolvers": "^3.2.0", "@hookform/resolvers": "^3.2.0",
"@testing-library/jest-dom": "^5.14.1", "@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0", "@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1", "@testing-library/user-event": "^13.2.1",
"antd": "^5.26.1",
"axios": "^1.4.0", "axios": "^1.4.0",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"dayjs": "^1.11.13",
"dotenv-cli": "^7.4.2", "dotenv-cli": "^7.4.2",
"framer-motion": "^12.19.1",
"i18next": "^23.15.1", "i18next": "^23.15.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"react": "^18.2.0", "react": "^18.2.0",

View File

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

View File

@@ -9,24 +9,33 @@ import {
AdminView, AdminView,
AuthSetting, AuthSetting,
AuthSettingUpdate, AuthSettingUpdate,
CaliumRequest, CaliumRequest, DataInitView,
LogView, LogView,
} from './pages/UserManage'; } from './pages/UserManage';
import { EconomicIndex, UserIndex } from './pages/IndexManage'; import { EconomicIndex, UserIndex } from './pages/IndexManage';
import { LandView, CryptView, GameLogView, UserView } 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,
ReportList, ReportList,
UserBlock, UserBlock,
UserBlockRegist, UserBlockRegist,
WhiteList,
LandAuction, LandAuction,
BattleEvent BattleEvent,
MenuBanner, MenuBannerRegist, Ranking,
Event
} from './pages/ServiceManage'; } from './pages/ServiceManage';
const RouteInfo = () => { const RouteInfo = () => {
@@ -49,6 +58,7 @@ const RouteInfo = () => {
<Route path="authsetting" element={<AuthSetting />} /> <Route path="authsetting" element={<AuthSetting />} />
<Route path="authsetting/:id" element={<AuthSettingUpdate />} /> <Route path="authsetting/:id" element={<AuthSettingUpdate />} />
<Route path="caliumrequest" element={<CaliumRequest />} /> <Route path="caliumrequest" element={<CaliumRequest />} />
<Route path="datainit" element={<DataInitView />} />
</Route> </Route>
<Route path="/indexmanage"> <Route path="/indexmanage">
<Route path="userindex" element={<UserIndex />} /> <Route path="userindex" element={<UserIndex />} />
@@ -56,23 +66,29 @@ const RouteInfo = () => {
</Route> </Route>
<Route path="/datamanage"> <Route path="/datamanage">
<Route path="userview" element={<UserView />} /> <Route path="userview" element={<UserView />} />
<Route path="landview" element={<LandView />} /> <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="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 />} />
<Route path="whitelist" element={<WhiteList />} />
<Route path="mail" element={<Mail />} /> <Route path="mail" element={<Mail />} />
<Route path="mail/mailregist" element={<MailRegist />} /> <Route path="mail/mailregist" element={<MailRegist />} />
<Route path="userblock" element={<UserBlock />} /> <Route path="userblock" element={<UserBlock />} />
<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/menubannerregist" element={<MenuBannerRegist />} />
<Route path="ranking" element={<Ranking />} />
<Route path="event" element={<Event />} />
</Route> </Route>
</Route> </Route>
</Routes> </Routes>

View File

@@ -130,3 +130,20 @@ export const BattleRewardView = async (token) => {
} }
} }
}; };
export const GameModeView = async (token) => {
try {
const res = await Axios.get(
`/api/v1/battle/game-mode/list`,
{
headers: { Authorization: `Bearer ${token}` },
},
);
return res.data.data.game_mode_list;
} catch (e) {
if (e instanceof Error) {
throw new Error('GameModeView Error', e);
}
}
};

View File

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

32
src/apis/Data.js Normal file
View File

@@ -0,0 +1,32 @@
//사용자 관리 - 데이터 api 연결
import { Axios } from '../utils';
export const InitData = async (token, params) => {
try {
const res = await Axios.post('/api/v1/data/init-data', params, {
headers: { Authorization: `Bearer ${token}` },
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('InitData Error', e);
}
}
};
export const InitHistoryList = async (token, params) => {
try {
const res = await Axios.post(`/api/v1/data/init-list`, params, {
headers: { Authorization: `Bearer ${token}` },
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('InitHistoryList Error', e);
}
}
};

95
src/apis/Dictionary.js Normal file
View 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);
}
}
};

View File

@@ -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,14 +51,14 @@ 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}` },
}); });
return res.data.data.list; return res.data;
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
throw new Error('EventModify Error', e); throw new Error('EventModify Error', e);
@@ -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);
} }
} }
}; };

View File

@@ -1,5 +1,4 @@
//사용자 관리 - 로그조회 api 연결 //사용자 관리 - 로그조회 api 연결
import { Axios } from '../utils'; import { Axios } from '../utils';
export const LogViewList = async (token, searchType, searchKey, historyType, startDt, endDt, orderBy, size, currentPage) => { export const LogViewList = async (token, searchType, searchKey, historyType, startDt, endDt, orderBy, size, currentPage) => {
@@ -13,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);
@@ -34,3 +33,21 @@ export const LogviewDetail = async (token, id) => {
} }
} }
}; };
export const LogHistory = async (token, params) => {
try {
const res = await Axios.post(`/api/v1/history/change-list`, params, {
headers: { Authorization: `Bearer ${token}` },
});
if(res.data.result === 'ERROR'){
throw new Error('LogHistory Error', res.data.data.message);
}
return res.data.data.list;
} catch (e) {
if (e instanceof Error) {
throw new Error('LogHistory Error', e);
}
}
};

View File

@@ -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}&currency_type=${currency_type}`, { const res = await Axios.get(`/api/v1/indicators/currency/list?start_dt=${start_dt}&end_dt=${end_dt}&currency_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}&currency_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);
} }
} }
}; };
@@ -321,136 +263,3 @@ export const InstanceIndexView = async (token, data, start_dt, end_dt) => {
} }
} }
}; };
// 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);
}
}
};

View File

@@ -2,26 +2,31 @@
import { Axios } from '../utils'; import { Axios } from '../utils';
//아이템 리스트 조회 export const ItemListAPI = async (token, params) => {
export const ItemListView = async (token, searchType, data, status, restore, order, size, currentPage) => {
try { try {
const res = await Axios.get( const res = await Axios.post(`/api/v1/items/list`, params, {
`/api/v1/items/list?search_type=${searchType ? searchType : ''} headers: { Authorization: `Bearer ${token}` },
&search_key=${data ? data : ''} });
&orderby=${order}
&page_no=${currentPage}
&page_size=${size}
`,
{
headers: { Authorization: `Bearer ${token}` },
},
);
// console.log(res.data.data);
return res.data.data; return res.data.data;
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
throw new Error('ItemAPI Error', e);
}
}
};
export const ItemDeleteAPI = async (token, params) => {
try {
const res = await Axios.delete(`/api/v1/items/delete`, {
headers: { Authorization: `Bearer ${token}` },
data: params,
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('ItemDelete Error', e);
} }
} }
}; };

View File

@@ -21,6 +21,24 @@ export const LandAuctionView = async (token, landType, landData, userType, userD
} }
}; };
export const LandInfoData = async (token, landType, landData, landSize, category, status, startDate, endDate, order, size, currentPage) => {
try {
const res = await Axios.get(
`/api/v1/land/info?land_type=${landType}&land_data=${landData}&land_size=${landSize}&category=${category}&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('LandInfoData Error', e);
}
}
};
// 랜드 경매 상세보기 // 랜드 경매 상세보기
export const LandAuctionDetailView = async (token, id) => { export const LandAuctionDetailView = async (token, id) => {
try { try {
@@ -51,6 +69,21 @@ export const LandAuctionSingleRegist = async (token, params) => {
} }
}; };
// 랜드 소유권 변경 등록
export const LandOwnedChangesRegist = async (token, params) => {
try {
const res = await Axios.post(`/api/v1/land/change`, params, {
headers: { Authorization: `Bearer ${token}` },
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('LandAuctionSingleRegist Error', e);
}
}
};
// 랜드 경매 수정 // 랜드 경매 수정
export const LandAuctionModify = async (token, id, params) => { export const LandAuctionModify = async (token, id, params) => {
try { try {
@@ -66,6 +99,21 @@ export const LandAuctionModify = async (token, id, params) => {
} }
}; };
// 랜드 소유권 변경 수정
export const LandOwnedChangesModify = async (token, id, params) => {
try {
const res = await Axios.put(`/api/v1/land/change/${id}`, params, {
headers: { Authorization: `Bearer ${token}` },
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('LandAuctionModify Error', e);
}
}
};
// 랜드 경매 삭제 // 랜드 경매 삭제
export const LandAuctionDelete = async (token, params, id) => { export const LandAuctionDelete = async (token, params, id) => {
try { try {
@@ -82,6 +130,22 @@ export const LandAuctionDelete = async (token, params, id) => {
} }
}; };
// 랜드 소유권 변경 예약 삭제
export const LandOwnerChangesDelete = async (token, params) => {
try {
const res = await Axios.delete(`/api/v1/land/change/delete`, {
headers: { Authorization: `Bearer ${token}` },
data: params,
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('LandAuctionDelete Error', e);
}
}
};
export const LandView = async (token) => { export const LandView = async (token) => {
try { try {
const res = await Axios.get( const res = await Axios.get(

313
src/apis/Log.js Normal file
View File

@@ -0,0 +1,313 @@
//운영 정보 관리 - 로그 api 연결
import { Axios, responseFileDownload } from '../utils';
// 비즈니스 로그 조회
export const BusinessLogList = async (token, params) => {
try {
const res = await Axios.post(`/api/v1/log/generic/list`, params, {
headers: { Authorization: `Bearer ${token}` },
timeout: 600000
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('BusinessLogList Error', e);
}
}
};
export const BusinessLogExport = async (token, params) => {
try {
await Axios.post(`/api/v1/log/generic/excel-export`, params, {
headers: { Authorization: `Bearer ${token}` },
responseType: 'blob'
}).then(response => {
responseFileDownload(response, {
defaultFileName: 'businessLog'
});
});
} catch (e) {
if (e instanceof Error) {
throw new Error('BusinessLogExport Error', e);
}
}
};
export const getExcelProgress = async (token, taskId) => {
try {
const response = await Axios.get(`/api/v1/log/progress/${taskId}`, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
if (!response.data) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response;
} catch (error) {
console.error('Progress API error:', error);
throw error;
}
};
export const getCurrencyList = async (token, startDate, endDate, order, size, currentPage) => {
try {
const response = await Axios.get(`/api/v1/log/currency/list?start_dt=${startDate}&end_dt=${endDate}
&orderby=${order}&page_no=${currentPage}&page_size=${size}`, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
return response.data;
} catch (error) {
console.error('getCurrencyList API error:', error);
throw error;
}
};
export const GameCurrencyLogExport = async (token, params, fileName) => {
try {
await Axios.post(`/api/v1/log/currency/excel-export`, params, {
headers: { Authorization: `Bearer ${token}` },
responseType: 'blob',
timeout: 300000
}).then(response => {
responseFileDownload(response, {
defaultFileName: fileName
});
});
} catch (e) {
if (e instanceof Error) {
throw new Error('GameCurrencyLogExport Error', e);
}
}
};
export const getCurrencyDetailList = async (token, searchType, searchData, tranId, logAction, currencyType, amountDeltaType, startDate, endDate, order, size, currentPage) => {
try {
const response = await Axios.get(`/api/v1/log/currency/detail/list?search_type=${searchType}&search_data=${searchData}&tran_id=${tranId}
&log_action=${logAction}&currency_type=${currencyType}&amount_delta_type=${amountDeltaType}&start_dt=${startDate}&end_dt=${endDate}
&orderby=${order}&page_no=${currentPage}&page_size=${size}`, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
return response.data;
} catch (error) {
console.error('getCurrencyDetailList API error:', error);
throw error;
}
};
export const GameCurrencyDetailLogExport = async (token, params, fileName) => {
try {
await Axios.post(`/api/v1/log/currency/detail/excel-export`, params, {
headers: { Authorization: `Bearer ${token}` },
responseType: 'blob',
timeout: 300000
}).then(response => {
responseFileDownload(response, {
defaultFileName: fileName
});
});
} catch (e) {
if (e instanceof Error) {
throw new Error('GameCurrencyDetailLogExport Error', e);
}
}
};
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}&currency_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);
}
}
};

View File

@@ -72,7 +72,7 @@ export const MailModify = async (token, id, params) => {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
return res.data.data.list; return res.data;
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
throw new Error('MailModify Error', e); throw new Error('MailModify Error', e);
@@ -88,7 +88,7 @@ export const MailDelete = async (token, params, id) => {
data: { list: params }, 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('MailDelete Error', e); throw new Error('MailDelete Error', e);
@@ -147,7 +147,7 @@ export const MailIsItem = async (token, params) => {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
return res; return res.data;
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
throw new Error('MailItemCheck Error', e); throw new Error('MailItemCheck Error', e);

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

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

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

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

99
src/apis/Rank.js Normal file
View 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
View 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);
}
}
};

View File

@@ -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);
@@ -42,7 +42,7 @@ export const UserChangeNickName = async (token, params) => {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
return res; return res.data;
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
throw new Error('UserChangeNickName Error', e); throw new Error('UserChangeNickName Error', e);
@@ -65,6 +65,20 @@ export const UserChangeAdminLevel = async (token, params) => {
} }
}; };
export const UserKick = async (token, params) => {
try {
const res = await Axios.put('/api/v1/users/user-kick', params, {
headers: { Authorization: `Bearer ${token}` },
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('UserKick Error', e);
}
}
};
// 아바타 조회 // 아바타 조회
export const UserAvatarView = async (token, guid) => { export const UserAvatarView = async (token, guid) => {
try { try {
@@ -171,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 {
@@ -187,9 +216,9 @@ export const UserFriendListView = async (token, guid) => {
}; };
// 우편 조회 // 우편 조회
export const UserMailView = async (token, guid, option) => { export const UserMailView = async (token, params) => {
try { try {
const res = await Axios.get(`/api/v1/users/mail?guid=${guid}&type=${option}`, { const res = await Axios.post(`/api/v1/users/mail`, params, {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });

View File

@@ -1,134 +0,0 @@
//운영서비스 관리 - 화이트 리스트 api 연결
import { Axios } from '../utils';
export const WhiteListData = async token => {
try {
const res = await Axios.get(`/api/v1/white-list/list`, {
headers: { Authorization: `Bearer ${token}` },
});
return res.data.data.list;
} catch (e) {
if (e instanceof Error) {
throw new Error('whiteList Error', e);
}
}
};
// 선택 삭제
export const WhiteListDelete = async (token, params) => {
try {
const res = await Axios.delete(`/api/v1/white-list`, {
headers: { Authorization: `Bearer ${token}` },
data: { list: params },
});
return res;
} catch (e) {
if (e instanceof Error) {
throw new Error('WhiteListDelete', e);
}
}
};
// 선택 승인
export const WhiteListAllow = async (token, params) => {
try {
const res = await Axios.patch(
`/api/v1/white-list`,
{ list: params },
{
headers: { Authorization: `Bearer ${token}` },
},
);
return res;
} catch (e) {
if (e instanceof Error) {
throw new Error('WhiteListAllow', e);
}
}
};
// 화이트 리스트 등록 (단일)
export const WhiteListRegist = async (token, params) => {
try {
const res = await Axios.post(`/api/v1/white-list`, params, {
headers: { Authorization: `Bearer ${token}` },
});
return res;
} catch (e) {
if (e instanceof Error) {
throw new Error('WhiteListRegist', e);
}
}
};
// 화이트리스트 엑셀 업로더
export const WhiteListExelUpload = async (token, file) => {
const exelFile = new FormData();
exelFile.append('file', file);
try {
const res = await Axios.post(`/api/v1/white-list/excel-upload`, exelFile, {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${token}`,
},
});
return res;
} catch (e) {
if (e instanceof Error) {
throw new Error('WhiteListExelUpload', e);
}
}
};
// 화이트 리스트 등록(복수) -> 등록하는 것임
export const WhiteListMultiRegsit = async (token, file) => {
const exelFile = new FormData();
exelFile.append('file', file);
try {
const res = await Axios.post(`/api/v1/white-list/multiPost`, exelFile, {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${token}`,
},
});
return res;
} catch (e) {
if (e instanceof Error) {
throw new Error('WhiteListMultiRegsit', e);
}
}
};
// 엑셀 다운로드
export const WhiteListExport = async (token, fileName) => {
try{
await Axios.get(`/api/v1/white-list/excelDownLoad`, {
headers: { Authorization: `Bearer ${token}` },
responseType: 'blob',
}).then(response => {
const href = URL.createObjectURL(response.data);
const link = document.createElement('a');
const fileName = 'Caliverse_whitelist.xlsx';
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('WhiteListExport Error', e);
}
}
};

View File

@@ -1,14 +1,42 @@
import { createAPIModule } from '../utils/apiService';
import * as APIConfigs from '../assets/data/apis'
export * from './Admin'; export * from './Admin';
export * from './Auth'; export * from './Auth';
export * from './Group'; export * from './Group';
export * from './History'; export * from './History';
export * from './Mail'; export * from './Mail';
export * from './Notice'; export * from './Notice';
export * from './WhiteList';
export * from './BlackList'; 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 './OpenAI';
export * from './Log';
export * from './Data';
export * from './Dictionary';
export * from './Rank';
export * from './Event';
const apiModules = {};
const allApis = {};
// 각 API 설정에 대해 모듈 생성
Object.entries(APIConfigs).forEach(([configName, config]) => {
const moduleName = configName.replace(/API$/, ''); // "userAPI" -> "user"
apiModules[moduleName] = createAPIModule(config);
// 모든 API 함수 추출해서 allApis에 복사
Object.entries(apiModules[moduleName]).forEach(([fnName, fn]) => {
allApis[fnName] = fn;
});
});
export const Modules = apiModules;
// export const ItemAPI = createAPIModule(itemAPIConfig);

View File

@@ -7,5 +7,14 @@ export const NONE = 'NONE';
export const ONE_MINUTE_MS = 60000; export const ONE_MINUTE_MS = 60000;
export const ONE_MINUTE_SECOND = 60; export const ONE_MINUTE_SECOND = 60;
export const AUCTION_MIN_MINUTE_TIME = 15; // 15분 export const AUCTION_MIN_MINUTE_TIME = 15; // 15분
export const IMAGE_MAX_SIZE = 5242880;
export const STORAGE_MAIL_COPY = 'copyMailData';
export const STORAGE_BUSINESS_LOG_SEARCH = 'businessLogSearchParam';
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 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 };

View 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"]
}
}
}

View File

@@ -0,0 +1,18 @@
{
"baseUrl": "/api/v1/history",
"endpoints": {
"LogViewList": {
"method": "GET",
"url": "/list",
"dataPath": "data",
"paramFormat": "query"
},
"LogviewDetail": {
"method": "GET",
"url": "/detail/:id",
"dataPath": "data.data",
"paramFormat": "query",
"paramMapping": ["id"]
}
}
}

View File

@@ -0,0 +1,15 @@
import itemAPI from './itemAPI.json';
import menuBannerAPI from './menuBannerAPI.json';
import historyAPI from './historyAPI.json';
import eventAPI from './eventAPI.json';
import rankingAPI from './rankingAPI.json';
import metaCraftingAPI from './metaCraftingAPI.json';
export {
itemAPI,
menuBannerAPI,
historyAPI,
eventAPI,
rankingAPI,
metaCraftingAPI
};

View File

@@ -0,0 +1,17 @@
{
"baseUrl": "/api/v1/items",
"endpoints": {
"ItemList": {
"method": "POST",
"url": "/list",
"dataPath": "data",
"paramFormat": "body"
},
"ItemDelete": {
"method": "DELETE",
"url": "/delete",
"dataPath": "data",
"paramFormat": "body"
}
}
}

View File

@@ -0,0 +1,48 @@
{
"baseUrl": "/api/v1/menu",
"endpoints": {
"MenuBannerView": {
"method": "GET",
"url": "/banner/list",
"dataPath": "data.data",
"paramFormat": "query"
},
"MenuBannerDetailView": {
"method": "GET",
"url": "/banner/detail/:id",
"dataPath": "data.data",
"paramFormat": "query",
"paramMapping": ["id"]
},
"MenuBannerSingleRegist": {
"method": "POST",
"url": "/banner",
"dataPath": "data",
"paramFormat": "body"
},
"MenuBannerModify": {
"method": "PUT",
"url": "/banner/:id",
"dataPath": "data",
"paramFormat": "body"
},
"MenuBannerDelete": {
"method": "DELETE",
"url": "/banner/delete",
"dataPath": "data",
"paramFormat": "body"
},
"MenuImageUpload": {
"method": "POST",
"url": "/image-upload",
"dataPath": "data",
"paramFormat": "body"
},
"MenuImageDelete": {
"method": "DELETE",
"url": "/image-delete",
"dataPath": "data",
"paramFormat": "body"
}
}
}

View File

@@ -0,0 +1,11 @@
{
"baseUrl": "/api/v1/dictionary/craft",
"endpoints": {
"getCraftingDictionaryList": {
"method": "GET",
"url": "/list",
"dataPath": "data",
"paramFormat": "query"
}
}
}

View 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"]
}
}
}

View File

@@ -5,6 +5,14 @@ export const benItems = [
"19010005" "19010005"
]; ];
export const historyBenField = [
"create_by",
"create_dt",
"update_by",
"update_dt",
"id"
]
export const HourList = ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23']; export const HourList = ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'];
export const MinuteList = [ export const MinuteList = [
@@ -24,17 +32,45 @@ export const caliumRequestInitData = {
content: '', content: '',
} }
export const ORDER_OPTIONS = {
asc: [
{ value: 'ASC', label: '오름차순' },
{ value: 'DESC', label: '내림차순' }
],
desc: [
{ value: 'DESC', label: '내림차순' },
{ value: 'ASC', label: '오름차순' }
],
};
export const PAGE_SIZE_OPTIONS = {
default: [
{ value: '50', label: '50개' },
{ value: '100', label: '100개' }
],
B: [
{ value: '500', label: '500개' },
{ value: '1000', label: '1000개' },
{ value: '5000', label: '5000개' },
{ value: '10000', label: '10000개' }
],
};
export const STATUS_STYLES = { export const STATUS_STYLES = {
COMPLETE: { COMPLETE: {
background: '#58AB62', background: '#58AB62',
color: 'white' color: 'white'
}, },
EXPIRATION: {
background: '#58AB62',
color: 'white'
},
WAIT: { WAIT: {
background: '#DEBB46', background: '#FAAD14',
color: 'black' color: 'black'
}, },
FAIL: { FAIL: {
background: '#D33B27', background: '#ff4d4f',
color: 'white' color: 'white'
}, },
FINISH: { FINISH: {
@@ -42,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: {
@@ -57,6 +93,10 @@ export const STATUS_STYLES = {
background: '#4287f5', background: '#4287f5',
color: 'white' color: 'white'
}, },
INPROGRESS: {
background: '#4287f5',
color: 'white'
},
AUCTION_END: { AUCTION_END: {
background: '#A37FB8', background: '#A37FB8',
color: 'white' color: 'white'
@@ -66,15 +106,115 @@ export const STATUS_STYLES = {
color: 'white' color: 'white'
}, },
REGISTER: { REGISTER: {
background: '#DEBB46', background: '#FAAD14',
color: 'black' color: 'black'
}, },
STOP: { STOP: {
background: '#FFB59B', background: '#FFB59B',
color: 'white' color: 'white'
}, },
DELETE: {
background: '#FFB59B',
color: 'white'
},
RUNNING: { RUNNING: {
background: '#4287f5', background: '#4287f5',
color: 'white' color: 'white'
}, },
}; };
export const FieldLabels = {
// DynamoDB 필드
'attribFieldName': '속성 명',
'pk': '파티션 키',
'sk': '정렬 키',
'DocType': '문서 타입',
'CreatedDateTime': '생성 일시',
'UpdatedDateTime': '수정 일시',
'DeletedDateTime': '삭제 일시',
'RestoredDateTime': '복원 일시',
'INSERT': '등록',
'UPDATE': '수정',
'DELETE': '삭제',
//기본
'id': 'ID',
'userId': '사용자 ID',
'userIP': '사용자 IP',
'timestamp': '타임스탬프',
'message': '메시지',
'tranId': '트랜잭션 ID',
'group_id': '그룹 ID',
'update_dt': '수정 일시',
'updateDt': '수정 일시',
'update_by': '수정자',
'create_dt': '생성 일시',
'createDt': '생성 일시',
'create_by': '생성자',
'status': '상태',
'deleted': '삭제 여부',
// 이벤트 필드 관련
'eventId': '이벤트 ID',
'eventName': '이벤트 명',
'repeatType': '반복 타입',
'eventOperationTime': '운영 시간(초)',
'eventStartDt': '시작 시간',
'eventEndDt': '종료 시간',
'roundTime': '라운드 시간(초)',
'roundCount': '라운드 수',
'hotTime': '핫타임',
'configId': '설정 ID',
'rewardGroupId': '보상 그룹 ID',
'attrib_type': '속성 타입',
'event_id': '이벤트 ID',
'is_active': '활성화 여부',
'start_day': '시작일',
'start_hour': '시작 시간',
'start_min': '시작 분',
'end_date': '종료 일시',
'instance_id': '인스턴스 ID',
'once_period_type': '주기 타입',
'day_of_week_type': '요일 타입',
'ffa_config_data_id': 'FFA 설정 ID',
'ffa_reward_group_id': 'FFA 보상 그룹 ID',
'ffa_hot_time': 'FFA 핫타임',
'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 = {
userBlock: 'black_list',
landAuction: 'land_auction',
landOwnerChange: 'land_ownership_changes',
rewardEvent: 'event',
mail: 'mail',
notice: 'notice',
battleEvent: 'battle_event',
caliumRequest: 'calium_request',
menuBanner: 'menu_banner',
event: 'world_event',
rankingSchedule: 'ranking_schedule',
}

View File

@@ -1,4 +1,4 @@
export {authType, ivenTabType, modalTypes, TabList, tattooSlot, commonStatus, ViewTitleCountType, landAuctionStatusType} from './types' export {authType, ivenTabType, modalTypes, tattooSlot, commonStatus, ViewTitleCountType, landAuctionStatusType} from './types'
export { export {
mailSendType, mailSendType,
mailType, mailType,
@@ -7,17 +7,27 @@ export {
adminLevelType, adminLevelType,
logOption, logOption,
eventStatus, eventStatus,
wellType, currencyItemCode,
blockStatus, blockStatus,
blockSanctions, blockSanctions,
blockPeriod, blockPeriod,
blockType, blockType,
caliumStatus, caliumStatus,
landSize, landSize,
userSearchType, userType,
landAuctionStatus, landAuctionStatus,
landSearchType, landSearchType,
CurrencyType, CurrencyType,
languageType languageType,
opLandCategoryType,
opLandOwnedType,
opSuccessType,
opPickupType,
opReadType,
opYNType,
opUserSessionType,
opMailType,
amountDeltaType,
TabUserList
} from './options' } from './options'
export {benItems, MinuteList, HourList, caliumRequestInitData, STATUS_STYLES, months} from './data' export {benItems, MinuteList, HourList, caliumRequestInitData, STATUS_STYLES, months, PAGE_SIZE_OPTIONS, ORDER_OPTIONS} from './data'

View File

@@ -1,4 +1,4 @@
import { authType } from './types'; import { adminAuthLevel, authType } from './types';
export const menuConfig = { export const menuConfig = {
usermanage: { usermanage: {
@@ -11,13 +11,17 @@ export const menuConfig = {
confirm: authType.adminSearchConfirm, confirm: authType.adminSearchConfirm,
update: authType.adminSearchUpdate, update: authType.adminSearchUpdate,
delete: authType.adminSearchDelete delete: authType.adminSearchDelete
} },
view: true,
authLevel: adminAuthLevel.NONE
}, },
logview: { logview: {
title: '사용 이력 조회', title: '사용 이력 조회',
permissions: { permissions: {
read: authType.adminLogSearchRead read: authType.adminLogSearchRead
} },
view: true,
authLevel: adminAuthLevel.NONE
}, },
authsetting: { authsetting: {
title: '권한 설정', title: '권한 설정',
@@ -25,14 +29,25 @@ export const menuConfig = {
read: authType.authoritySettingRead, read: authType.authoritySettingRead,
update: authType.authoritySettingUpdate, update: authType.authoritySettingUpdate,
delete: authType.authoritySettingDelete delete: authType.authoritySettingDelete
} },
view: true,
authLevel: adminAuthLevel.NONE
}, },
caliumrequest: { caliumrequest: {
title: '칼리움 요청', title: '칼리움 요청',
permissions: { permissions: {
read: authType.caliumRequestRead, read: authType.caliumRequestRead,
update: authType.caliumRequestUpdate update: authType.caliumRequestUpdate
} },
view: true,
authLevel: adminAuthLevel.NONE
},
datainit: {
title: '데이터 초기화',
permissions: {},
view: false,
test: true,
authLevel: adminAuthLevel.MASTER
} }
} }
}, },
@@ -43,13 +58,17 @@ export const menuConfig = {
title: '유저 지표', title: '유저 지표',
permissions: { permissions: {
read: authType.userIndicatorsRead read: authType.userIndicatorsRead
} },
view: true,
authLevel: adminAuthLevel.NONE
}, },
economicindex: { economicindex: {
title: '경제 지표', title: '경제 지표',
permissions: { permissions: {
read: authType.economicIndicatorsRead read: authType.economicIndicatorsRead
} },
view: true,
authLevel: adminAuthLevel.NONE
} }
} }
}, },
@@ -62,27 +81,59 @@ export const menuConfig = {
read: authType.userSearchRead, read: authType.userSearchRead,
update: authType.userSearchUpdate, update: authType.userSearchUpdate,
delete: authType.userSearchDelete delete: authType.userSearchDelete
} },
view: true,
authLevel: adminAuthLevel.NONE
}, },
landview: { landview: {
title: '랜드 조회', title: '랜드 정보 조회',
permissions: { permissions: {
read: authType.landRead, read: authType.landRead,
update: authType.landUpdate, update: authType.landUpdate,
delete: authType.landDelete delete: authType.landDelete
} },
view: true,
authLevel: adminAuthLevel.NONE
}, },
gamelogview: { gamelogview: {
title: '게임 로그 조회', title: '게임 로그 조회',
permissions: { permissions: {
read: authType.gameLogRead read: authType.gameLogRead
} },
view: false,
authLevel: adminAuthLevel.NONE
}, },
cryptview: { businesslogview: {
title: '크립토 조회', title: '비즈니스 로그 조회',
permissions: { permissions: {
read: authType.cryptoRead read: authType.businessLogRead
} },
view: true,
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
} }
} }
}, },
@@ -95,7 +146,9 @@ export const menuConfig = {
read: authType.inGameRead, read: authType.inGameRead,
update: authType.inGameUpdate, update: authType.inGameUpdate,
delete: authType.inGameDelete delete: authType.inGameDelete
} },
view: true,
authLevel: adminAuthLevel.NONE
}, },
mail: { mail: {
title: '우편', title: '우편',
@@ -103,7 +156,9 @@ export const menuConfig = {
read: authType.mailRead, read: authType.mailRead,
update: authType.mailUpdate, update: authType.mailUpdate,
delete: authType.mailDelete delete: authType.mailDelete
} },
view: true,
authLevel: adminAuthLevel.NONE
}, },
userblock: { userblock: {
title: '이용자 제재', title: '이용자 제재',
@@ -111,23 +166,29 @@ export const menuConfig = {
read: authType.blackListRead, read: authType.blackListRead,
update: authType.blackListUpdate, update: authType.blackListUpdate,
delete: authType.blackListDelete delete: authType.blackListDelete
} },
view: true,
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,
event: { // authLevel: adminAuthLevel.NONE
// },
rewardevent: {
title: '보상 이벤트 관리', title: '보상 이벤트 관리',
permissions: { permissions: {
read: authType.eventRead, read: authType.eventRead,
update: authType.eventUpdate, update: authType.eventUpdate,
delete: authType.eventDelete delete: authType.eventDelete
} },
view: true,
authLevel: adminAuthLevel.NONE
}, },
landauction: { landauction: {
title: '랜드 경매 관리', title: '랜드 경매 관리',
@@ -135,7 +196,9 @@ export const menuConfig = {
read: authType.landAuctionRead, read: authType.landAuctionRead,
update: authType.landAuctionUpdate, update: authType.landAuctionUpdate,
delete: authType.landAuctionDelete delete: authType.landAuctionDelete
} },
view: true,
authLevel: adminAuthLevel.NONE
}, },
battleevent: { battleevent: {
title: '전투시스템 타입 스케줄러', title: '전투시스템 타입 스케줄러',
@@ -143,7 +206,49 @@ export const menuConfig = {
read: authType.battleEventRead, read: authType.battleEventRead,
update: authType.battleEventUpdate, update: authType.battleEventUpdate,
delete: authType.battleEventDelete delete: authType.battleEventDelete
} },
view: true,
authLevel: adminAuthLevel.NONE
},
items: {
title: '아이템 관리',
permissions: {
read: authType.itemRead,
update: authType.itemUpdate,
delete: authType.itemDelete
},
view: true,
authLevel: adminAuthLevel.NONE
},
menubanner: {
title: '메뉴 배너 관리',
permissions: {
read: authType.menuBannerRead,
update: authType.menuBannerUpdate,
delete: authType.menuBannerDelete
},
view: true,
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
}, },
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,96 @@
{
"initialSearchParams": {
"searchType": "GUID",
"searchData": "",
"logAction": "None",
"logDomain": "BASE",
"tran_id": "",
"startDate": "",
"endDate": "",
"orderBy": "DESC",
"pageSize": 500,
"currentPage": 1
},
"searchFields": [
{
"type": "select",
"id": "searchType",
"optionsRef": "userSearchType2",
"col": 1,
"required": true
},
{
"type": "text",
"id": "searchData",
"placeholder": "대상 입력",
"width": "300px",
"col": 1,
"required": true
},
{
"type": "period",
"startDateId": "startDate",
"endDateId": "endDate",
"label": "조회 일자",
"col": 1
},
{
"type": "text",
"id": "searchContent",
"label": "우편 내용",
"placeholder": "우편 내용(공백으로 구분)",
"width": "300px",
"col": 1
},
{
"type": "select",
"id": "sendType",
"label": "발송 방식",
"optionsRef": "mailSendType",
"col": 2
},
{
"type": "select",
"id": "status",
"label": "발송 상태",
"optionsRef": "mailSendStatus",
"col": 2
},
{
"type": "select",
"id": "mailType",
"label": "우편 타입",
"optionsRef": "mailType",
"col": 2
},
{
"type": "select",
"id": "receiveType",
"label": "수신 대상",
"optionsRef": "mailReceiveType",
"col": 2
}
],
"apiInfo": {
"functionName": "MailView",
"loadOnMount": true,
"paramsMapping": [
"searchTitle",
"searchContent",
"sendType",
"status",
"mailType",
"receiveType",
{"param": "startDate", "transform": "toISOString"},
{"param": "endDate", "transform": "toISOString"},
"orderBy",
"pageSize",
"currentPage"
],
"pageField": "currentPage",
"pageSizeField": "pageSize",
"orderField": "orderBy"
}
}

View File

@@ -0,0 +1,53 @@
{
"initialSearchParams": {
"searchContent": "",
"status": "ALL",
"startDate": "",
"endDate": "",
"orderBy": "DESC",
"pageSize": 50,
"currentPage": 1
},
"searchFields": [
{
"type": "period",
"startDateId": "startDate",
"endDateId": "endDate",
"label": "등록 일자",
"col": 1
},
{
"type": "text",
"id": "searchContent",
"label": "요청 내용",
"placeholder": "요청 내용",
"width": "300px",
"col": 1
},
{
"type": "select",
"id": "status",
"label": "요청 상태",
"optionsRef": "caliumStatus",
"col": 1
}
],
"apiInfo": {
"functionName": "CaliumRequestView",
"loadOnMount": true,
"paramsMapping": [
"searchContent",
"status",
{"param": "startDate", "transform": "toISOString"},
{"param": "endDate", "transform": "toISOString"},
"orderBy",
"pageSize",
"currentPage"
],
"pageField": "currentPage",
"pageSizeField": "pageSize",
"orderField": "orderBy"
}
}

View File

@@ -0,0 +1,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": "EventView",
"loadOnMount": true,
"paramTransforms": [
{"param": "startDate", "transform": "toISOString"},
{"param": "endDate", "transform": "toISOString"}
],
"pageField": "page_no",
"pageSizeField": "page_size",
"orderField": "orderBy"
}
}

View 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"
}
}

View File

@@ -0,0 +1,59 @@
{
"initialSearchParams": {
"searchType": "ID",
"searchData": "",
"historyType": "",
"startDate": "today",
"endDate": "today",
"orderBy": "DESC",
"pageSize": 50,
"currentPage": 1
},
"searchFields": [
{
"type": "select",
"id": "searchType",
"label": "대상",
"optionsRef": "adminUserType",
"col": 1
},
{
"type": "text",
"id": "searchData",
"placeholder": "대상 입력",
"width": "300px",
"col": 1
},
{
"type": "select",
"id": "historyType",
"label": "이력종류",
"optionsRef": "opHistoryType",
"col": 1
},
{
"type": "period",
"startDateId": "startDate",
"endDateId": "endDate",
"label": "기간",
"col": 2,
"width": "500px",
"format": "YYYY-MM-DD HH:mm:ss",
"showTime": true
}
],
"apiInfo": {
"endpointName": "LogViewList",
"loadOnMount": true,
"paramTransforms": [
{"param": "startDate", "transform": "toISOString"},
{"param": "endDate", "transform": "toISOString"}
],
"pageField": "page_no",
"pageSizeField": "page_size",
"orderField": "orderBy"
},
"paginationType": "front"
}

View File

@@ -0,0 +1,69 @@
{
"id": "historyTable",
"selection": {
"type": "single",
"idField": "id"
},
"header": {
"countType": "total",
"orderType": "desc",
"pageType": "default",
"buttons": []
},
"columns": [
{
"id": "logTime",
"type": "date",
"width": "200px",
"title": "일시(KST)",
"format": {
"type": "function",
"name": "convertKTC"
}
},
{
"id": "category",
"type": "option",
"width": "100px",
"title": "로그종류",
"option_name": "opLogCategory"
},
{
"id": "action",
"type": "option",
"width": "150px",
"title": "로그액션",
"option_name": "opLogAction"
},
{
"id": "status",
"type": "option",
"width": "100px",
"title": "상태",
"option_name": "opCommonStatus"
},
{
"id": "worker",
"type": "text",
"width": "100px",
"title": "작업자"
},
{
"id": "message",
"type": "text",
"width": "100px",
"title": "비고"
},
{
"id": "detail",
"type": "button",
"width": "120px",
"title": "상세보기",
"text": "상세보기"
}
],
"sort": {
"defaultColumn": "timestamp",
"defaultDirection": "desc"
}
}

View File

@@ -0,0 +1,39 @@
{
"initialSearchParams": {
"searchType": "GUID",
"searchData": "",
"orderBy": "DESC",
"pageSize": 50,
"currentPage": 1,
"lastEvaluatedKey": null
},
"searchFields": [
{
"type": "select",
"id": "searchType",
"label": "대상",
"optionsRef": "userType",
"col": 1,
"required": true
},
{
"type": "text",
"id": "searchData",
"placeholder": "대상 입력",
"width": "300px",
"col": 1,
"required": true
}
],
"apiInfo": {
"endpointName": "ItemList",
"loadOnMount": false,
"pageField": "page_no",
"pageSizeField": "page_size",
"orderField": "orderBy",
"lastPageKeyField": "pageKey"
},
"paginationType": "dynamodb",
"initSearch": false
}

View File

@@ -0,0 +1,90 @@
{
"id": "itemTable",
"selection": {
"type": "single",
"idField": "id"
},
"header": {
"countType": "total",
"orderType": "desc",
"pageType": "default",
"buttons": [
{
"id": "delete",
"text": "선택 삭제",
"theme": "line",
"disableWhen": "noSelection",
"requiredAuth": "itemDelete",
"action": "delete"
},
{
"id": "restore",
"text": "아이템 복구",
"theme": "line",
"disableWhen": "disable",
"requiredAuth": "itemDelete",
"action": "restore"
}
]
},
"columns": [
{
"id": "checkbox",
"type": "checkbox",
"width": "40px",
"title": ""
},
{
"id": "item_name",
"type": "text",
"width": "20%",
"title": "아이템명"
},
{
"id": "item_id",
"type": "text",
"width": "20%",
"title": "아이템 ID"
},
{
"id": "count",
"type": "text",
"width": "80px",
"title": "수량"
},
{
"id": "item_type",
"type": "option",
"width": "120px",
"title": "아이템 타입",
"option_name": "opItemType"
},
{
"id": "equip_type",
"type": "option",
"width": "80px",
"title": "장착",
"option_name": "opEquipType"
},
{
"id": "equiped_pos",
"type": "text",
"width": "80px",
"title": "슬롯"
},
{
"id": "create_dt",
"type": "date",
"width": "100px",
"title": "생성날짜(KST)",
"format": {
"type": "function",
"name": "convertKTC"
}
}
],
"sort": {
"defaultColumn": "row_num",
"defaultDirection": "desc"
}
}

View File

@@ -0,0 +1,90 @@
{
"initialSearchParams": {
"searchTitle": "",
"searchContent": "",
"sendType": "ALL",
"status": "ALL",
"mailType": "ALL",
"receiveType": "ALL",
"startDate": "",
"endDate": "",
"orderBy": "DESC",
"pageSize": 50,
"currentPage": 1
},
"searchFields": [
{
"type": "text",
"id": "searchTitle",
"label": "우편 제목",
"placeholder": "제목 입력",
"width": "300px",
"col": 1
},
{
"type": "period",
"startDateId": "startDate",
"endDateId": "endDate",
"label": "조회 일자",
"col": 1
},
{
"type": "text",
"id": "searchContent",
"label": "우편 내용",
"placeholder": "우편 내용(공백으로 구분)",
"width": "300px",
"col": 1
},
{
"type": "select",
"id": "sendType",
"label": "발송 방식",
"optionsRef": "mailSendType",
"col": 2
},
{
"type": "select",
"id": "status",
"label": "발송 상태",
"optionsRef": "mailSendStatus",
"col": 2
},
{
"type": "select",
"id": "mailType",
"label": "우편 타입",
"optionsRef": "mailType",
"col": 2
},
{
"type": "select",
"id": "receiveType",
"label": "수신 대상",
"optionsRef": "mailReceiveType",
"col": 2
}
],
"apiInfo": {
"functionName": "MailView",
"loadOnMount": true,
"paramsMapping": [
"searchTitle",
"searchContent",
"sendType",
"status",
"mailType",
"receiveType",
{"param": "startDate", "transform": "toISOString"},
{"param": "endDate", "transform": "toISOString"},
"orderBy",
"pageSize",
"currentPage"
],
"pageField": "currentPage",
"pageSizeField": "pageSize",
"orderField": "orderBy"
}
}

View File

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

View File

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

View File

@@ -0,0 +1,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": "MenuBannerView",
"loadOnMount": true,
"paramTransforms": [
{"param": "startDate", "transform": "toISOString"},
{"param": "endDate", "transform": "toISOString"}
],
"pageField": "page_no",
"pageSizeField": "page_size",
"orderField": "orderBy"
}
}

View File

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

View 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"
}
}

View 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"
}
}

View 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"
}
}

View File

@@ -0,0 +1,63 @@
{
"initialSearchParams": {
"searchTitle": "",
"searchContent": "",
"status": "ALL",
"startDate": "",
"endDate": "",
"orderBy": "DESC",
"pageSize": 50,
"currentPage": 1
},
"searchFields": [
{
"type": "text",
"id": "searchTitle",
"label": "우편 제목",
"placeholder": "제목 입력",
"width": "300px",
"col": 1
},
{
"type": "period",
"startDateId": "startDate",
"endDateId": "endDate",
"label": "조회 일자",
"col": 1
},
{
"type": "text",
"id": "searchContent",
"label": "우편 내용",
"placeholder": "우편 내용(공백으로 구분)",
"width": "300px",
"col": 1
},
{
"type": "select",
"id": "status",
"label": "이벤트 상태",
"optionsRef": "eventStatus",
"col": 2
}
],
"apiInfo": {
"functionName": "RewardEventView",
"loadOnMount": true,
"paramsMapping": [
"searchTitle",
"searchContent",
"status",
{"param": "startDate", "transform": "toISOString"},
{"param": "endDate", "transform": "toISOString"},
"orderBy",
"pageSize",
"currentPage"
],
"pageField": "currentPage",
"pageSizeField": "pageSize",
"orderField": "orderBy"
}
}

View File

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

View File

@@ -0,0 +1,102 @@
{
"id": "userBlockTable",
"selection": {
"type": "single",
"idField": "id"
},
"header": {
"countType": "total",
"orderType": "desc",
"pageType": "default",
"buttons": [
{
"id": "delete",
"text": "선택 해지",
"theme": "line",
"disableWhen": "noSelection",
"requiredAuth": "blackListDelete",
"action": "delete"
},
{
"id": "register",
"text": "제재 등록",
"theme": "primary",
"requiredAuth": "blackListUpdate",
"action": "navigate",
"navigateTo": "/servicemanage/userblock/userblockregist"
}
]
},
"columns": [
{
"id": "checkbox",
"type": "checkbox",
"width": "40px",
"title": ""
},
{
"id": "row_num",
"type": "text",
"width": "80px",
"title": "번호"
},
{
"id": "guid",
"type": "text",
"width": "20%",
"title": "GUID"
},
{
"id": "nickname",
"type": "text",
"width": "20%",
"title": "아바타명"
},
{
"id": "status",
"type": "status",
"width": "100px",
"title": "상태",
"option_name": "blockStatus"
},
{
"id": "period",
"type": "option",
"width": "100px",
"title": "제재 기간",
"option_name": "blockPeriod"
},
{
"id": "sanctions",
"type": "option",
"width": "250px",
"title": "제재 사유",
"option_name": "blockSanctions"
},
{
"id": "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"
}
}

View File

@@ -1,4 +1,5 @@
export const authType = { export const authType = {
none: 0,
adminSearchRead: 1, adminSearchRead: 1,
adminSearchConfirm: 2, adminSearchConfirm: 2,
adminSearchUpdate: 3, adminSearchUpdate: 3,
@@ -46,22 +47,43 @@ export const authType = {
landDelete: 45, landDelete: 45,
battleEventRead: 46, battleEventRead: 46,
battleEventUpdate: 47, battleEventUpdate: 47,
battleEventDelete: 48 battleEventDelete: 48,
businessLogRead: 49,
menuBannerRead: 50,
menuBannerUpdate: 51,
menuBannerDelete: 52,
itemDictionaryRead: 53,
rankManagerRead: 54,
rankManagerUpdate: 55,
rankingRead: 56,
rankingUpdate: 57,
rankingDelete: 58,
worldEventRead: 59,
worldEventUpdate: 60,
worldEventDelete: 61,
craftingDictionaryRead: 62,
levelReader: 999,
levelMaster: 9999,
levelDeveloper: 99999,
}; };
export const TabList = [ export const alertTypes = {
{ title: '기본정보' }, info: 0,
{ title: '아바타' }, success: 1,
{ title: '의상' }, warning: 2,
{ title: '도구' }, error: 3,
{ title: '인벤토리' }, confirm: 4,
{ title: '우편' }, confirmChildren: 5
{ title: '마이홈' }, }
{ title: '친구목록' },
{ title: '타투' }, export const adminAuthLevel = {
{ title: '퀘스트' }, NONE: "None",
// { title: '클레임' }, READER: "Reader",
]; MASTER: "Master",
DEVELOPER: "Developer",
}
export const ivenTabType = { export const ivenTabType = {
CLOTH: "cloth", CLOTH: "cloth",
@@ -93,6 +115,13 @@ export const commonStatus = {
delete: "DELETE", delete: "DELETE",
reject: "REJECT", reject: "REJECT",
complete: "COMPLETE", complete: "COMPLETE",
cancel: "CANCEL"
}
export const customStatus = {
inprogress: "INPROGRESS",
expiration: "EXPIRATION",
} }
export const ViewTitleCountType = { export const ViewTitleCountType = {
@@ -126,3 +155,9 @@ export const currencyCodeTypes = {
ruby: "19010005", ruby: "19010005",
calium: "19010003" calium: "19010003"
} }
export const languageNames = {
'Ko': '한국어',
'En': '영어',
'Ja': '일본어',
};

View File

@@ -1,198 +0,0 @@
import { styled } from 'styled-components';
import { useState } from 'react';
import Button from '../../components/common/button/Button';
import DatePicker, { registerLocale } from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import { getMonth, getYear } from 'date-fns';
import range from 'lodash/range';
import { TextInput, SelectInput, DatePickerWrapper, InputLabel, BtnWrapper } from '../../styles/Components';
const GoodsLogSearchBar = () => {
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(new Date());
const [selectData, setSelectData] = useState('default');
const years = range(1990, getYear(new Date()) + 1, 1);
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
const handleChange = e => {
setSelectData(e.target.value);
};
return (
<>
<SearchbarStyle2>
<SearchRow>
<SearchItem>
<InputLabel>조회 기간</InputLabel>
<DatePickerWrapper>
<InputGroup>
<DatePicker
selected={startDate}
onChange={date => setStartDate(date)}
className="datepicker"
placeholderText="검색기간 선택"
calendarClassName="calendar"
dateFormat="yyyy - MM - dd"
locale="ko"
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
<div className="calendar-top">
<button
className="btn-prev"
onClick={e => {
e.preventDefault();
decreaseMonth();
}}
disabled={prevMonthButtonDisabled}></button>
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
{years.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
.
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
{months.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<button
className="btn-next"
onClick={e => {
e.preventDefault();
increaseMonth();
}}
disabled={nextMonthButtonDisabled}></button>
</div>
)}
/>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
</InputGroup>
<span>~</span>
<InputGroup>
<DatePicker
selected={endDate}
onChange={date => setEndDate(date)}
className="datepicker"
placeholderText="검색기간 선택"
calendarClassName="calendar"
dateFormat="yyyy - MM - dd"
minDate = {startDate}
locale="ko"
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
<div className="calendar-top">
<button
className="btn-prev"
onClick={e => {
e.preventDefault();
decreaseMonth();
}}
disabled={prevMonthButtonDisabled}></button>
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
{years.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
.
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
{months.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<button
className="btn-next"
onClick={e => {
e.preventDefault();
increaseMonth();
}}
disabled={nextMonthButtonDisabled}></button>
</div>
)}
/>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
</InputGroup>
</DatePickerWrapper>
</SearchItem>
</SearchRow>
<SearchRow>
<SearchItem>
<InputLabel>조회 대상</InputLabel>
<TextInput type="text" placeholder="조회 대상 유저의 GUID를 입력하세요." width="600px" />
</SearchItem>
<BtnWrapper $gap="8px">
<Button theme="reset" />
<Button theme="gray" text="검색" />
</BtnWrapper>
</SearchRow>
</SearchbarStyle2>
</>
);
};
export default GoodsLogSearchBar;
const SearchbarStyle = styled.div`
width: 100%;
display: flex;
flex-wrap: wrap;
gap: 20px 0;
font-size: 14px;
padding: 20px;
border-top: 1px solid #000;
border-bottom: 1px solid #000;
margin-bottom: 40px;
`;
const SearchbarStyle2 = styled(SearchbarStyle)`
flex-flow: column;
gap: 20px;
`;
const SearchRow = styled.div`
display: flex;
flex-wrap: wrap;
gap: 20px 0;
`;
const InputGroup = styled.div`
display: flex;
align-items: center;
gap: 5px;
`;
const SearchItem = styled.div`
display: flex;
align-items: center;
gap: 20px;
margin-right: 50px;
${TextInput}, ${SelectInput} {
height: 35px;
}
${TextInput} {
padding: 0 10px;
max-width: 400px;
}
`;

View 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;

View File

@@ -0,0 +1,166 @@
import React, { Fragment, useEffect, useMemo, useRef, useState } from 'react';
import styled from 'styled-components';
import {
CircularProgressWrapper,
FormWrapper,
TableStyle,
TableWrapper,
} from '../../styles/Components';
import { amountDeltaType, CurrencyType } from '../../assets/data';
import { useTranslation } from 'react-i18next';
import { numberFormatter } from '../../utils';
import { TableSkeleton } from '../Skeleton/TableSkeleton';
import { CurrencyLogSearchBar, useCurrencyLogSearch } from '../searchBar';
import { TopButton, ViewTableInfo } from '../common';
import Pagination from '../common/Pagination/Pagination';
import {
INITIAL_PAGE_LIMIT,
STORAGE_BUSINESS_LOG_SEARCH,
STORAGE_GAME_LOG_CURRENCY_SEARCH,
} from '../../assets/data/adminConstants';
import ExcelExportButton from '../common/button/ExcelExportButton';
import CircularProgress from '../common/CircularProgress';
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
} = useCurrencyLogSearch(token, 500);
useEffect(() => {
if(active) {
// 세션 스토리지에서 복사된 메일 데이터 가져오기
const paramsData = sessionStorage.getItem(STORAGE_GAME_LOG_CURRENCY_SEARCH);
if (paramsData) {
const searchData = JSON.parse(paramsData);
handleSearch({
start_dt: new Date(searchData.start_dt),
end_dt: new Date(searchData.end_dt),
search_data: searchData.guid
});
// 사용 후 세션 스토리지 데이터 삭제
sessionStorage.removeItem(STORAGE_GAME_LOG_CURRENCY_SEARCH);
}
}
}, [active]);
const tableHeaders = useMemo(() => {
return [
// { id: 'logDay', label: '일자', width: '120px' },
{ id: 'logTime', label: '일시', width: '120px' },
{ id: 'accountId', label: 'account ID', width: '80px' },
{ id: 'userGuid', label: 'GUID', width: '200px' },
{ id: 'userNickname', label: '아바타명', width: '150px' },
{ id: 'tranId', label: '트랜잭션 ID', width: '200px' },
{ id: 'action', label: '액션', width: '150px' },
{ id: 'currencyType', label: '재화종류', width: '120px' },
{ id: 'amountDeltaType', label: '증감유형', width: '120px' },
{ id: 'deltaAmount', label: '수량', width: '120px' },
// { id: 'deltaAmount', label: '수량원본', width: '120px' },
{ id: 'currencyAmount', label: '잔량', width: '120px' },
];
}, []);
if(!active) return null;
return (
<AnimatedPageWrapper>
<FormWrapper>
<CurrencyLogSearchBar
searchParams={searchParams}
onSearch={(newParams, executeSearch = true) => {
if (executeSearch) {
handleSearch(newParams);
} else {
updateSearchParams(newParams);
}
}}
onReset={handleReset}
/>
</FormWrapper>
<ViewTableInfo orderType="asc" pageType="B" total={dataList?.total} total_all={dataList?.total_all}>
<ExcelExportButton
functionName="GameCurrencyDetailLogExport"
params={searchParams}
fileName={t('FILE_GAME_LOG_CURRENCY')}
onLoadingChange={setDownloadState}
dataSize={dataList?.total_all}
/>
{downloadState.loading && (
<CircularProgressWrapper>
<CircularProgress
progress={downloadState.progress}
size={36}
progressColor="#4A90E2"
/>
</CircularProgressWrapper>
)}
</ViewTableInfo>
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
<>
<TableWrapper>
<TableStyle ref={tableRef}>
<thead>
<tr>
{tableHeaders.map(header => (
<th key={header.id} width={header.width}>{header.label}</th>
))}
</tr>
</thead>
<tbody>
{dataList?.currency_detail_list?.map((item, index) => (
<Fragment key={index}>
<tr>
<td>{item.logTime}</td>
<td>{item.accountId}</td>
<td>{item.userGuid}</td>
<td>{item.userNickname}</td>
<td>{item.tranId}</td>
<td>{item.action}</td>
<td>{CurrencyType.find(data => data.value === item.currencyType)?.name}</td>
<td>{amountDeltaType.find(data => data.value === item.amountDeltaType)?.name}</td>
<td>{numberFormatter.formatCurrency(item.deltaAmount)}</td>
{/*<td>{item.deltaAmount}</td>*/}
<td>{numberFormatter.formatCurrency(item.currencyAmount)}</td>
</tr>
</Fragment>
))}
</tbody>
</TableStyle>
</TableWrapper>
{dataList?.currency_detail_list &&
<Pagination
postsPerPage={searchParams.page_size}
totalPosts={dataList?.total_all}
setCurrentPage={handlePageChange}
currentPage={searchParams.page_no}
pageLimit={INITIAL_PAGE_LIMIT}
/>
}
<TopButton />
</>
}
</AnimatedPageWrapper>
);
};
export default CurrencyLogContent;

View 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;

View File

@@ -1,219 +0,0 @@
import { useState } from 'react';
import { styled } from 'styled-components';
import RadioInput from '../common/input/Radio';
import Button from '../common/button/Button';
import DatePicker, { registerLocale } from 'react-datepicker';
import { ko } from 'date-fns/esm/locale';
import 'react-datepicker/dist/react-datepicker.css';
import { getMonth, getYear } from 'date-fns';
import range from 'lodash/range';
import { TextInput, SelectInput, DatePickerWrapper, InputLabel, BtnWrapper } from '../../styles/Components';
const ItemLogSearchBar = () => {
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(new Date());
const [selectData, setSelectData] = useState('default');
const years = range(1990, getYear(new Date()) + 1, 1);
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
const handleChange = e => {
setSelectData(e.target.value);
};
return (
<>
<SearchbarStyle2>
<SearchRow>
<RadioGroup>
<RadioInput label="기본 조회" id="single" name="receiver" value="default" fontWeight="600" checked={selectData === 'default'} handleChange={handleChange} />
<RadioInput label="아이템 소유자 추적" id="multi" name="receiver" value="item" fontWeight="600" checked={selectData === 'item'} handleChange={handleChange} />
</RadioGroup>
</SearchRow>
<SearchRow>
<SearchItem>
<InputLabel>조회 기간</InputLabel>
<DatePickerWrapper>
<InputGroup>
<DatePicker
selected={startDate}
onChange={date => setStartDate(date)}
className="datepicker"
placeholderText="검색기간 선택"
calendarClassName="calendar"
dateFormat="yyyy - MM - dd"
locale="ko"
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
<div className="calendar-top">
<button
className="btn-prev"
onClick={e => {
e.preventDefault();
decreaseMonth();
}}
disabled={prevMonthButtonDisabled}></button>
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
{years.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
.
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
{months.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<button
className="btn-next"
onClick={e => {
e.preventDefault();
increaseMonth();
}}
disabled={nextMonthButtonDisabled}></button>
</div>
)}
/>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
</InputGroup>
<span>~</span>
<InputGroup>
<DatePicker
selected={endDate}
onChange={date => setEndDate(date)}
className="datepicker"
placeholderText="검색기간 선택"
calendarClassName="calendar"
dateFormat="yyyy - MM - dd"
minDate = {startDate}
locale="ko"
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
<div className="calendar-top">
<button
className="btn-prev"
onClick={e => {
e.preventDefault();
decreaseMonth();
}}
disabled={prevMonthButtonDisabled}></button>
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
{years.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
.
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
{months.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<button
className="btn-next"
onClick={e => {
e.preventDefault();
increaseMonth();
}}
disabled={nextMonthButtonDisabled}></button>
</div>
)}
/>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
</InputGroup>
</DatePickerWrapper>
</SearchItem>
</SearchRow>
<SearchRow>
{selectData === 'default' ? (
<SearchItem>
<InputLabel>조회 대상</InputLabel>
<TextInput type="text" placeholder="조회 대상 유저의 GUID를 입력하세요." width="600px" />
</SearchItem>
) : (
<SearchItem>
<InputLabel>아이템 ID</InputLabel>
<TextInput type="text" placeholder="아이템의 GUID를 입력하세요." width="600px" />
</SearchItem>
)}
<BtnWrapper $gap="8px">
<Button theme="reset" />
<Button theme="gray" text="검색" />
</BtnWrapper>
</SearchRow>
</SearchbarStyle2>
</>
);
};
export default ItemLogSearchBar;
const SearchbarStyle = styled.div`
width: 100%;
display: flex;
flex-wrap: wrap;
gap: 20px 0;
font-size: 14px;
padding: 20px;
border-top: 1px solid #000;
border-bottom: 1px solid #000;
margin-bottom: 40px;
`;
const SearchbarStyle2 = styled(SearchbarStyle)`
flex-flow: column;
gap: 20px;
`;
const SearchRow = styled.div`
display: flex;
flex-wrap: wrap;
gap: 20px 0;
`;
const InputGroup = styled.div`
display: flex;
align-items: center;
gap: 5px;
`;
const RadioGroup = styled(InputGroup)`
gap: 30px;
height: 35px;
`;
const SearchItem = styled.div`
display: flex;
align-items: center;
gap: 20px;
margin-right: 50px;
${TextInput}, ${SelectInput} {
height: 35px;
}
${TextInput} {
padding: 0 10px;
max-width: 400px;
}
`;

View File

@@ -2,16 +2,19 @@ import { styled } from 'styled-components';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Title } from '../../styles/Components'; import { Title } from '../../styles/Components';
import { TextInput, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components'; import { TextInput, BtnWrapper } 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 { UserChangeNickName } from '../../apis'; import { UserChangeNickName } from '../../apis';
import { alertTypes } from '../../assets/data/types';
import { useAlert } from '../../context/AlertProvider';
import { useLoading } from '../../context/LoadingProvider';
const NicknameChangeModal = ({ pwPop, handleClick, dataList }) => { const NicknameChangeModal = ({ pwPop, handleClick, dataList }) => {
const {showModal, showToast} = useAlert();
const {withLoading} = useLoading();
let nickName = dataList.char_info && dataList.char_info.character_name; let nickName = dataList.char_info && dataList.char_info.character_name;
const [modifyModal, setModifyModal] = useState('hidden');
const [completeModal, setCompleteModal] = useState('hidden');
const [completeText, setCompleteText] = useState('');
const [resultData, setResultData] = useState({ const [resultData, setResultData] = useState({
guid: '', guid: '',
@@ -20,42 +23,42 @@ const NicknameChangeModal = ({ pwPop, handleClick, dataList }) => {
}); });
useEffect(() => { useEffect(() => {
setResultData({ ...resultData, guid: dataList.user_info && dataList.user_info.aid, nickname: dataList.char_info && dataList.char_info.character_name }); setResultData({ ...resultData,
guid: dataList.user_info && dataList.user_info.aid,
nickname: dataList.char_info && dataList.char_info.character_name
});
}, [dataList]); }, [dataList]);
// 수정 모달창 const handleSubmit = async (type, param = null) => {
const handleModifyModal = () => { switch (type) {
if (modifyModal === 'hidden') {
setModifyModal('view'); case "nicknameChangeSubmit":
} else { showModal('NICKNAME_CHANGES_CONFIRM', {
setModifyModal('hidden'); type: alertTypes.confirm,
onConfirm: () => handleSubmit('nicknameChange')
});
break;
case "nicknameChange":
const token = sessionStorage.getItem('token');
await withLoading(async () => {
return await UserChangeNickName(token, resultData);
}).then(data =>{
console.log(data);
if(data.result === 'ERROR'){
showToast(data.data.message, {type: alertTypes.error});
}else{
showToast('NICKNAME_CHANGES_COMPLETE', {type: alertTypes.success});
}
}).catch(error => {
showToast(error, {type: alertTypes.error});
}).finally(() => {
handleClick();
});
break;
} }
}; }
// 완료 모달창
const handleCompleteModal = () => {
if (completeModal === 'hidden') {
setCompleteModal('view');
} else {
setCompleteModal('hidden');
handleClick();
completeText === '변경이 완료되었습니다.' && window.location.reload();
}
};
// 수정
const handleModifyNotice = async () => {
const token = sessionStorage.getItem('token');
const message = await UserChangeNickName(token, resultData);
// console.log(message);
message.data.data.message !== '수정 하였습니다.' ? setCompleteText('변경 닉네임이 이미 존재합니다.\n다시 시도해주세요.') : setCompleteText('변경이 완료되었습니다.');
handleCompleteModal();
handleModifyModal();
};
return ( return (
<> <>
@@ -82,29 +85,7 @@ const NicknameChangeModal = ({ pwPop, handleClick, dataList }) => {
</PwSetTable> </PwSetTable>
<BtnWrapper $justify="center" $gap="10px"> <BtnWrapper $justify="center" $gap="10px">
<Button theme="line" text="취소" handleClick={handleClick} /> <Button theme="line" text="취소" handleClick={handleClick} />
<Button theme="primary" text="변경하기" handleClick={handleModifyModal} /> <Button theme="primary" text="변경하기" handleClick={() => handleSubmit('nicknameChangeSubmit')} />
</BtnWrapper>
</Modal>
{/* 확인 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={modifyModal}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleModifyModal} />
</BtnWrapper>
<ModalText $align="center">닉네임을 변경하시겠습니까?</ModalText>
<BtnWrapper $gap="10px">
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleModifyModal} />
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleModifyNotice} />
</BtnWrapper>
</Modal>
{/* 완료 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={completeModal}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleCompleteModal} />
</BtnWrapper>
<ModalText $align="center">{completeText}</ModalText>
<BtnWrapper $gap="10px">
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleCompleteModal} />
</BtnWrapper> </BtnWrapper>
</Modal> </Modal>
</> </>

View File

@@ -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>
); );

View File

@@ -1,198 +0,0 @@
import { styled } from 'styled-components';
import { useState } from 'react';
import Button from '../../components/common/button/Button';
import DatePicker, { registerLocale } from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import { getMonth, getYear } from 'date-fns';
import range from 'lodash/range';
import { TextInput, SelectInput, DatePickerWrapper, InputLabel, BtnWrapper } from '../../styles/Components';
const TradeLogSerchBar = () => {
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(new Date());
const [selectData, setSelectData] = useState('default');
const years = range(1990, getYear(new Date()) + 1, 1);
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
const handleChange = e => {
setSelectData(e.target.value);
};
return (
<>
<SearchbarStyle2>
<SearchRow>
<SearchItem>
<InputLabel>조회 기간</InputLabel>
<DatePickerWrapper>
<InputGroup>
<DatePicker
selected={startDate}
onChange={date => setStartDate(date)}
className="datepicker"
placeholderText="검색기간 선택"
calendarClassName="calendar"
dateFormat="yyyy - MM - dd"
locale="ko"
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
<div className="calendar-top">
<button
className="btn-prev"
onClick={e => {
e.preventDefault();
decreaseMonth();
}}
disabled={prevMonthButtonDisabled}></button>
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
{years.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
.
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
{months.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<button
className="btn-next"
onClick={e => {
e.preventDefault();
increaseMonth();
}}
disabled={nextMonthButtonDisabled}></button>
</div>
)}
/>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
</InputGroup>
<span>~</span>
<InputGroup>
<DatePicker
selected={endDate}
onChange={date => setEndDate(date)}
className="datepicker"
placeholderText="검색기간 선택"
calendarClassName="calendar"
dateFormat="yyyy - MM - dd"
minDate = {startDate}
locale="ko"
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
<div className="calendar-top">
<button
className="btn-prev"
onClick={e => {
e.preventDefault();
decreaseMonth();
}}
disabled={prevMonthButtonDisabled}></button>
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
{years.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
.
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
{months.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<button
className="btn-next"
onClick={e => {
e.preventDefault();
increaseMonth();
}}
disabled={nextMonthButtonDisabled}></button>
</div>
)}
/>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
<SelectInput>
<option value="">00</option>
<option value="">01</option>
</SelectInput>
</InputGroup>
</DatePickerWrapper>
</SearchItem>
</SearchRow>
<SearchRow>
<SearchItem>
<InputLabel>조회 대상</InputLabel>
<TextInput type="text" placeholder="조회 대상 유저의 GUID를 입력하세요." width="600px" />
</SearchItem>
<BtnWrapper $gap="8px">
<Button theme="reset" />
<Button theme="gray" text="검색" />
</BtnWrapper>
</SearchRow>
</SearchbarStyle2>
</>
);
};
export default TradeLogSerchBar;
const SearchbarStyle = styled.div`
width: 100%;
display: flex;
flex-wrap: wrap;
gap: 20px 0;
font-size: 14px;
padding: 20px;
border-top: 1px solid #000;
border-bottom: 1px solid #000;
margin-bottom: 40px;
`;
const SearchbarStyle2 = styled(SearchbarStyle)`
flex-flow: column;
gap: 20px;
`;
const SearchRow = styled.div`
display: flex;
flex-wrap: wrap;
gap: 20px 0;
`;
const InputGroup = styled.div`
display: flex;
align-items: center;
gap: 5px;
`;
const SearchItem = styled.div`
display: flex;
align-items: center;
gap: 20px;
margin-right: 50px;
${TextInput}, ${SelectInput} {
height: 35px;
}
${TextInput} {
padding: 0 10px;
max-width: 400px;
}
`;

View File

@@ -8,7 +8,9 @@ const UserAvatarInfo = ({ userInfo }) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
fetchData(); if(userInfo && Object.keys(userInfo).length > 0) {
fetchData();
}
}, []); }, []);
const fetchData = async () => { const fetchData = async () => {

View 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;

View File

@@ -1,11 +1,11 @@
import { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import Profile from '../../assets/img/datamanage/img-profile.png'; import Profile from '../../assets/img/datamanage/img-profile.png';
import NicknameChangeModal from '../../components/DataManage/NicknameChangeModal'; import NicknameChangeModal from '../../components/DataManage/NicknameChangeModal';
import EditIcon from '../../assets/img/icon/icon-edit.png'; import EditIcon from '../../assets/img/icon/icon-edit.png';
import { UserChangeAdminLevel, UserInfoView } from '../../apis/Users'; import { UserChangeAdminLevel, UserInfoView, UserKick } from '../../apis/Users';
import { SelectInput } from '../../styles/Components'; import { SelectInput } from '../../styles/Components';
import { adminLevelType, authType, modalTypes } from '../../assets/data'; import { adminLevelType, authType, modalTypes } from '../../assets/data';
import DynamicModal from '../common/modal/DynamicModal'; import DynamicModal from '../common/modal/DynamicModal';
@@ -16,53 +16,96 @@ import { convertKTC } from '../../utils';
import { EditButton, ProfileWrapper, UserDefault, UserInfoTable } from '../../styles/ModuleComponents'; import { EditButton, ProfileWrapper, UserDefault, UserInfoTable } from '../../styles/ModuleComponents';
import { TableSkeleton } from '../Skeleton/TableSkeleton'; import { TableSkeleton } from '../Skeleton/TableSkeleton';
import { UserInfoSkeleton } from '../Skeleton/UserInfoSkeleton'; import { UserInfoSkeleton } from '../Skeleton/UserInfoSkeleton';
import { opUserSessionType } from '../../assets/data/options';
import Button from '../common/button/Button';
import { useModal } from '../../hooks/hook';
import { InitData } from '../../apis/Data';
import { useAlert } from '../../context/AlertProvider';
import { useLoading } from '../../context/LoadingProvider';
import { alertTypes } from '../../assets/data/types';
const UserDefaultInfo = ({ userInfo }) => { const UserDefaultInfo = ({ userInfo }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const authInfo = useRecoilValue(authList); const authInfo = useRecoilValue(authList);
const token = sessionStorage.getItem('token');
const {showModal, showToast} = useAlert();
const {withLoading} = useLoading();
const [pwPop, setPwPop] = useState('hidden'); const {
const [gmModal, setGmModal] = useState('hidden'); modalState,
handleModalView,
handleModalClose
} = useModal({
pwChange: 'hidden'
});
const [dataList, setDataList] = useState({}); const [dataList, setDataList] = useState({});
const [adminLevel, setAdminLevel] = useState('0');
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [authDelete, setAuthDelete] = useState(false);
const handleClick = () => {
if (pwPop === 'hidden') setPwPop('view');
else setPwPop('hidden');
};
useEffect(() => { useEffect(() => {
fetchData(); setAuthDelete(authInfo?.auth_list?.some(auth => auth.id === authType.userSearchDelete));
}, [authInfo]);
useEffect(() => {
if(userInfo && Object.keys(userInfo).length > 0) {
fetchData();
}
}, [userInfo]); }, [userInfo]);
const fetchData = async () => { const fetchData = async () => {
const token = sessionStorage.getItem('token'); setLoading(true);
await UserInfoView(token, userInfo.guid).then(data => { await UserInfoView(token, userInfo.guid).then(data => {
setDataList(data); setDataList(data);
setLoading(false); setLoading(false);
}); });
}; };
const handleGMChange = (e) =>{ const handleSubmit = async (type, param = null) => {
setAdminLevel(e.target.value);
setGmModal('view');
}
const handleSubmit = async () => {
const token = sessionStorage.getItem('token');
let params = {}; let params = {};
params.guid = userInfo.guid;
params.admin_level = adminLevel;
await UserChangeAdminLevel(token, params); switch (type) {
case "gmLevelChangeSubmit":
showModal('USER_GM_CHANGE', {
type: alertTypes.confirm,
onConfirm: () => handleSubmit('gmLevelChange', param)
});
break;
handleCancel(); case "userKickSubmit":
await fetchData(); showModal('USER_KICK_CONFIRM', {
} type: alertTypes.confirm,
onConfirm: () => handleSubmit('userKick')
});
break;
const handleCancel = () => { case "gmLevelChange":
setGmModal('hidden'); params.guid = userInfo.guid;
params.admin_level = param;
await withLoading(async () => {
return await UserChangeAdminLevel(token, params);
}).then(data =>{
showToast('USER_GM_CHANGE_COMPLETE', {type: alertTypes.success});
}).catch(error => {
showToast(error, {type: alertTypes.error});
}).finally(() => {
fetchData();
});
break;
case "userKick":
params.guid = userInfo.guid;
await withLoading(async () => {
return await UserKick(token, params);
}).then((data) =>{
showToast('USER_KICK_COMPLETE', {type: alertTypes.success});
}).catch(error => {
showToast(error, {type: alertTypes.error});
}).finally(() => {
fetchData();
});
break;
}
} }
return ( return (
@@ -76,11 +119,11 @@ const UserDefaultInfo = ({ userInfo }) => {
<UserInfoTable $maxwidth="530px"> <UserInfoTable $maxwidth="530px">
<tbody> <tbody>
<tr> <tr>
<th>AID(GUID)</th> <th>GUID</th>
<td>{dataList.user_info && dataList.user_info.aid}</td> <td>{dataList.user_info && dataList.user_info.aid}</td>
</tr> </tr>
<tr> <tr>
<th>계정 ID</th> <th>Account ID</th>
<td>{dataList.user_info && dataList.user_info.user_id}</td> <td>{dataList.user_info && dataList.user_info.user_id}</td>
</tr> </tr>
<tr> <tr>
@@ -88,12 +131,17 @@ const UserDefaultInfo = ({ userInfo }) => {
<td>{dataList.user_info && dataList.user_info.nation}</td> <td>{dataList.user_info && dataList.user_info.nation}</td>
</tr> </tr>
<tr> <tr>
<th>멤버십</th> <th>접속상태</th>
<td>{dataList.user_info && dataList.user_info.membership}</td> <StatusCell>{dataList.user_session !== undefined && opUserSessionType.find(session => session.value === dataList.user_session)?.name}
</tr> {<Button
<tr> theme={(dataList.user_session && authDelete) ? "line" : "disable"}
<th>친구 추천코드</th> id={"user_session"}
<td>{dataList.user_info && dataList.user_info.friend_code}</td> name="kick"
text="KICK"
handleClick={() => handleSubmit('userKickSubmit')}
disabled={(!authDelete || !dataList.user_session)}
/>}
</StatusCell>
</tr> </tr>
<tr> <tr>
<th>계정 생성일</th> <th>계정 생성일</th>
@@ -103,10 +151,7 @@ const UserDefaultInfo = ({ userInfo }) => {
</tr> </tr>
<tr> <tr>
<th>최근 접속일자</th> <th>최근 접속일자</th>
<td> <td>{dataList.user_info && convertKTC(dataList.user_info.access_dt)}</td>
{/*{dataList.user_info && String(new Date(new Date(dataList.user_info.access_dt).setHours(new Date(dataList.user_info.access_dt).getHours() + 9)).toLocaleString())}*/}
{dataList.user_info && convertKTC(dataList.user_info.access_dt)}
</td>
</tr> </tr>
<tr> <tr>
<th>최근 종료일자</th> <th>최근 종료일자</th>
@@ -121,7 +166,9 @@ const UserDefaultInfo = ({ userInfo }) => {
<tr> <tr>
<th>GM권한</th> <th>GM권한</th>
<td> <td>
<SelectInput value={dataList.user_info && dataList.user_info.admin_level} onChange={(e) => handleGMChange(e)} disabled={authInfo.auth_list && !authInfo.auth_list.some(auth => auth.id === authType.userSearchUpdate)} > <SelectInput value={dataList?.user_info?.admin_level}
onChange={e => handleSubmit('gmLevelChangeSubmit', e.target.value)}
disabled={authInfo.auth_list && !authInfo.auth_list.some(auth => auth.id === authType.userSearchUpdate)} >
{adminLevelType.map((data, index) => ( {adminLevelType.map((data, index) => (
<option key={index} value={data.value}> <option key={index} value={data.value}>
{data.name} {data.name}
@@ -146,10 +193,10 @@ const UserDefaultInfo = ({ userInfo }) => {
<td colSpan="3"> <td colSpan="3">
{dataList.char_info && dataList.char_info.character_name} {dataList.char_info && dataList.char_info.character_name}
<EditButton <EditButton
hidden={true} // hidden={true}
onClick={e => { onClick={e => {
e.preventDefault(); e.preventDefault();
handleClick(); handleModalView('pwChange');
}}></EditButton> }}></EditButton>
</td> </td>
</tr> </tr>
@@ -172,15 +219,21 @@ const UserDefaultInfo = ({ userInfo }) => {
</tbody> </tbody>
</UserInfoTable> </UserInfoTable>
</div> </div>
<NicknameChangeModal pwPop={pwPop} handleClick={handleClick} dataList={dataList} /> <NicknameChangeModal
<DynamicModal pwPop={modalState.pwChangeModal}
modalType={modalTypes.childOkCancel} handleClick={() => {
view={gmModal} handleModalClose('pwChange');
modalText={t('USER_GM_CHANGE')} fetchData();
handleCancel={handleCancel} }}
handleSubmit={handleSubmit} dataList={dataList} />
/>
</> </>
); );
}; };
export default UserDefaultInfo; export default UserDefaultInfo;
const StatusCell = styled.td`
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
`;

View File

@@ -9,7 +9,9 @@ const UserDressInfo = ({ userInfo }) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
fetchData(); if(userInfo && Object.keys(userInfo).length > 0) {
fetchData();
}
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -91,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;

View File

@@ -13,7 +13,9 @@ const UserFriendInfo = ({ userInfo }) => {
const [dataList, setDataList] = useState([]); const [dataList, setDataList] = useState([]);
useEffect(() => { useEffect(() => {
fetchData(); if(userInfo && Object.keys(userInfo).length > 0) {
fetchData();
}
}, []); }, []);
const fetchData = async () => { const fetchData = async () => {

View File

@@ -12,10 +12,16 @@ import { useRecoilValue } from 'recoil';
import { authList } from '../../store/authList'; import { authList } from '../../store/authList';
import { TableSkeleton } from '../Skeleton/TableSkeleton'; import { TableSkeleton } from '../Skeleton/TableSkeleton';
import { InfoSubTitle, UserDefaultTable, UserTableWrapper } from '../../styles/ModuleComponents'; import { InfoSubTitle, UserDefaultTable, UserTableWrapper } from '../../styles/ModuleComponents';
import { useAlert } from '../../context/AlertProvider';
import { useLoading } from '../../context/LoadingProvider';
import { convertKTC, timeDiffMinute } from '../../utils';
import { alertTypes } from '../../assets/data/types';
const UserInventoryInfo = ({ userInfo }) => { const UserInventoryInfo = ({ userInfo }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const authInfo = useRecoilValue(authList); const authInfo = useRecoilValue(authList);
const {showModal, showToast} = useAlert();
const {withLoading} = useLoading();
const [dataList, setDataList] = useState(); const [dataList, setDataList] = useState();
const [itemCount, setItemCount] = useState(''); const [itemCount, setItemCount] = useState('');
@@ -28,8 +34,10 @@ const UserInventoryInfo = ({ userInfo }) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
fetchData(); if(userInfo && Object.keys(userInfo).length > 0) {
}, []); fetchData();
}
}, [userInfo]);
useEffect(() => { useEffect(() => {
setAuthDelete(authInfo.auth_list.some(auth => auth.id === 35)); setAuthDelete(authInfo.auth_list.some(auth => auth.id === 35));
@@ -152,6 +160,19 @@ const UserInventoryInfo = ({ userInfo }) => {
} }
}; };
const handleAction = async (action, item = null) => {
switch (action) {
case "delete":
break;
case "deleteConfirm":
break;
default:
break;
}
};
const ConfirmChild = () => { const ConfirmChild = () => {
return( return(
<InputItem> <InputItem>
@@ -173,7 +194,7 @@ const UserInventoryInfo = ({ userInfo }) => {
<th width="120">ID</th> <th width="120">ID</th>
<th width="50%">아이템</th> <th width="50%">아이템</th>
<th width="100">보유개수</th> <th width="100">보유개수</th>
<th width="60">삭제</th> {/*<th width="60">삭제</th>*/}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -184,9 +205,9 @@ const UserInventoryInfo = ({ userInfo }) => {
<td>{el.item_id}</td> <td>{el.item_id}</td>
<td>{el.item_name}</td> <td>{el.item_name}</td>
<td>{el.count}</td> <td>{el.count}</td>
<td> {/*<td>*/}
<Button theme={authDelete ? "line" : "disable"} id={el.item_guid} name="single" text="삭제" handleClick={e => {handleDeleteConfirmModal(el, type)}} disabled={!authDelete}/> {/* <Button theme={authDelete ? "line" : "disable"} id={el.item_guid} name="single" text="삭제" handleClick={e => {handleDeleteConfirmModal(el, type)}} disabled={!authDelete}/>*/}
</td> {/*</td>*/}
</tr> </tr>
); );
})} })}
@@ -225,136 +246,6 @@ const UserInventoryInfo = ({ userInfo }) => {
modalText={t('DEL_COMPLETE')} modalText={t('DEL_COMPLETE')}
handleComplete={handleDeleteComplete} handleComplete={handleDeleteComplete}
/> />
{/* <InfoSubTitle>의상 탭</InfoSubTitle>
<UserTableWrapper>
<UserDefaultTable>
<thead>
<tr>
<th width="80">no.</th>
<th width="120">ID</th>
<th width="50%">아이템</th>
<th width="100">보유개수</th>
</tr>
</thead>
<tbody>
{dataList &&
dataList.inventory_list.cloth.map((el, idx) => {
return (
<tr key={idx}>
<td>{idx + 1}</td>
<td>{el.item_id}</td>
<td>{el.item_name}</td>
<td>{el.count}</td>
</tr>
);
})}
</tbody>
</UserDefaultTable>
</UserTableWrapper>
<InfoSubTitle>소품 탭</InfoSubTitle>
<UserTableWrapper>
<UserDefaultTable>
<thead>
<tr>
<th width="80">no.</th>
<th width="120">ID</th>
<th width="50%">아이템</th>
<th width="100">보유개수</th>
</tr>
</thead>
<tbody>
{dataList &&
dataList.inventory_list.prop.map((el, idx) => {
return (
<tr key={idx}>
<td>{idx + 1}</td>
<td>{el.item_id}</td>
<td>{el.item_name}</td>
<td>{el.count}</td>
</tr>
);
})}
</tbody>
</UserDefaultTable>
</UserTableWrapper>
<InfoSubTitle>미용 탭</InfoSubTitle>
<UserTableWrapper>
<UserDefaultTable>
<thead>
<tr>
<th width="80">no.</th>
<th width="120">ID</th>
<th width="50%">아이템</th>
<th width="100">보유개수</th>
</tr>
</thead>
<tbody>
{dataList &&
dataList.inventory_list.beauty.map((el, idx) => {
return (
<tr key={idx}>
<td>{idx + 1}</td>
<td>{el.item_id}</td>
<td>{el.item_name}</td>
<td>{el.count}</td>
</tr>
);
})}
</tbody>
</UserDefaultTable>
</UserTableWrapper>
<InfoSubTitle>타투 탭</InfoSubTitle>
<UserTableWrapper>
<UserDefaultTable>
<thead>
<tr>
<th width="80">no.</th>
<th width="120">ID</th>
<th width="50%">아이템</th>
<th width="100">보유개수</th>
</tr>
</thead>
<tbody>
{dataList &&
dataList.inventory_list.tattoo.map((el, idx) => {
return (
<tr key={idx}>
<td>{idx + 1}</td>
<td>{el.item_id}</td>
<td>{el.item_name}</td>
<td>{el.count}</td>
</tr>
);
})}
</tbody>
</UserDefaultTable>
</UserTableWrapper>
<InfoSubTitle>기타 탭</InfoSubTitle>
<UserTableWrapper>
<UserDefaultTable>
<thead>
<tr>
<th width="80">슬롯 no.</th>
<th width="320">ID</th>
<th width="50%">아이템</th>
<th width="100">보유개수</th>
</tr>
</thead>
<tbody>
{dataList &&
dataList.inventory_list.etc.map((el, idx) => {
return (
<tr key={idx}>
<td>{idx + 1}</td>
<td>{el.item_id}</td>
<td>{el.item_name}</td>
<td>{el.count}</td>
</tr>
);
})}
</tbody>
</UserDefaultTable>
</UserTableWrapper> */}
</> </>
); );
}; };

View 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;

View File

@@ -1,108 +1,120 @@
import { useState, Fragment, useEffect } from 'react'; import { useState, Fragment, useEffect, memo } from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import CheckBox from '../../components/common/input/CheckBox';
import MailDetailModal from '../../components/DataManage/MailDetailModal'; import MailDetailModal from '../../components/DataManage/MailDetailModal';
import { SelectInput, TextInput } from '../../styles/Components'; import { SelectInput, TextInput } from '../../styles/Components';
import { UserMailDelete, UserMailItemDelete, UserMailView } from '../../apis'; import { UserMailDelete, UserMailItemDelete, UserMailView } from '../../apis';
import ConfirmModal from '../common/modal/ConfirmModal';
import CompletedModal from '../common/modal/CompletedModal';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import CustomConfirmModal from '../common/modal/CustomConfirmModal'; import CustomConfirmModal from '../common/modal/CustomConfirmModal';
import DynamicModal from '../common/modal/DynamicModal'; import { authType, ivenTabType, opMailType } from '../../assets/data';
import { authType, ivenTabType } from '../../assets/data';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { authList } from '../../store/authList'; import { authList } from '../../store/authList';
import { convertKTC } from '../../utils'; import { convertKTC } from '../../utils';
import { TableSkeleton } from '../Skeleton/TableSkeleton'; import { TableSkeleton } from '../Skeleton/TableSkeleton';
import { opPickupType, opReadType, opYNType } from '../../assets/data/options';
import { useDynamoDBPagination, useModal } from '../../hooks/hook';
import { DynamoPagination } from '../common';
import { useLoading } from '../../context/LoadingProvider';
import { useAlert } from '../../context/AlertProvider';
import { alertTypes } from '../../assets/data/types';
const ConfirmChild = memo(({ maxCount }) => {
const { t } = useTranslation();
const [localCount, setLocalCount] = useState('1');
const {showToast} = useAlert();
const handleChange = (e) => {
let value = e.target.value;
if (value === '0' || value === '-0') {
value = '1';
} else if (value < 0) {
value = Math.abs(value).toString();
} else if(Number(value) > maxCount) {
showToast('DEL_COUNT_CHECK', {type: alertTypes.warning});
value = maxCount.toString();
}
setLocalCount(value);
};
return(
<InputItem>
<p>{t('DEL_COUNT_CONFIRM', {count: maxCount})}</p>
<TextInput
id="user-mail-item-count-input"
placeholder="수량"
type="number"
value={localCount}
onChange={handleChange}
width="200px"
/>
</InputItem>
);
});
const UserMailInfo = ({ userInfo }) => { const UserMailInfo = ({ userInfo }) => {
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const { t } = useTranslation();
const authInfo = useRecoilValue(authList); const authInfo = useRecoilValue(authList);
const {withLoading} = useLoading();
const {showModal, showToast} = useAlert();
//데이터 리스트
const [dataList, setDataList] = useState([]);
// 받은 우편, 보낸 우편 // 받은 우편, 보낸 우편
const [option, setOption] = useState('RECEIVE'); const [option, setOption] = useState('RECEIVE');
// 상세 정보
const [detail, setDetail] = useState({ title: '', content: '', item_list: [], mail_guid: '' }); const [detail, setDetail] = useState({ title: '', content: '', item_list: [], mail_guid: '' });
const [deleteSelected, setDeleteSelected] = useState({}); const [deleteSelected, setDeleteSelected] = useState({});
const [itemUpdateCount, setItemUpdateCount] = useState('1');
const [authDelete, setAuthDelete] = useState(false); const [authDelete, setAuthDelete] = useState(false);
const [loading, setLoading] = useState(true);
const [modalState, setModalState] = useState({ const {
detailModal: 'hidden', modalState,
deleteItemModal: 'hidden', handleModalView,
deleteSubmitModal: 'hidden', handleModalClose
deleteCompleteModal: 'hidden', } = useModal({
deleteItemCompleteModal: 'hidden' detail: 'hidden',
deleteItem: 'hidden'
}); });
// 받은 우편, 보낸 우편 option 에 따른 data fetch const fetchMailData = async (page, startKey) => {
const params = {
mail_type: option,
guid: userInfo.guid,
page_key: startKey
};
return await UserMailView(token, params);
};
const {
data: dataList,
loading,
pagination,
fetchPage,
goToNextPage,
goToPrevPage,
resetPagination
} = useDynamoDBPagination(fetchMailData);
useEffect(() => { useEffect(() => {
fetchData(option); resetPagination();
fetchPage(1);
}, [option]); }, [option]);
useEffect(() => { useEffect(() => {
setAuthDelete(authInfo.auth_list.some(auth => auth.id === authType.userSearchDelete)); setAuthDelete(authInfo.auth_list.some(auth => auth.id === authType.userSearchDelete));
}, [authInfo]); }, [authInfo]);
const fetchData = async option => {
await UserMailView(token, userInfo.guid, option).then(data =>{
setDataList(data);
setLoading(false);
});
};
// 상세 모달 value 세팅
const handleDetail = (title, content, itmeList, mail_guid) => { const handleDetail = (title, content, itmeList, mail_guid) => {
setDetail({ title: title, content: content, item_list: itmeList, mail_guid: mail_guid }); setDetail({ title: title, content: content, item_list: itmeList, mail_guid: mail_guid });
}; };
// 우편 아이템 삭제 개수 처리
const handleItemCount = e => {
if (e.target.value === '0' || e.target.value === '-0') {
setItemUpdateCount('1');
e.target.value = '1';
} else if (e.target.value < 0) {
let plusNum = Math.abs(e.target.value);
setItemUpdateCount(plusNum);
} else if(e.target.value > deleteSelected.count){
alert(t('DEL_COUNT_CHECK'));
setItemUpdateCount(deleteSelected.count);
} else {
setItemUpdateCount(e.target.value);
}
};
const handleModalView = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'view',
}));
}
const handleModalClose = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'hidden',
}));
}
const handleModalSubmit = async (type, param = null) => { const handleModalSubmit = async (type, param = null) => {
let params; let params;
let result;
switch (type) { switch (type) {
case "detail": case "detail":
handleModalView('deleteSubmit'); showModal('USER_MAIL_DEL_CONFIRM', {
break; type: alertTypes.confirm,
case "deleteItem": onConfirm: () => handleModalSubmit('deleteSubmit')
setDeleteSelected(param); });
handleModalView('deleteItem');
break; break;
case "deleteSubmit": case "deleteSubmit":
params = {} params = {}
@@ -110,76 +122,89 @@ const UserMailInfo = ({ userInfo }) => {
params.guid = userInfo.guid; params.guid = userInfo.guid;
params.mail_guid = detail.mail_guid; params.mail_guid = detail.mail_guid;
result = await UserMailDelete(token, params); await withLoading(async () => {
return await UserMailDelete(token, params);
}).then(data => {
if(data.result === "SUCCESS") {
showToast('DEL_COMPLETE', {type: alertTypes.success});
}else{
showToast(data.data.message, {type: alertTypes.error});
}
}).catch(e => {
showToast(e, {type: alertTypes.error});
});
handleModalClose('deleteSubmit'); break;
handleModalView('deleteComplete'); case "deleteItem":
setDeleteSelected(param);
handleModalView('deleteItem');
break; break;
case "deleteItemSubmit": case "deleteItemSubmit":
//랜더링 문제로 dom에서 값을 찾아 가져온다.
const inputElement = document.getElementById("user-mail-item-count-input");
if(inputElement === null) showToast('INPUT_VALUE_ERROR',{type: alertTypes.error});
const count = inputElement.value;
params = {} params = {}
params.type = option; params.type = option;
params.guid = userInfo.guid; params.guid = userInfo.guid;
params.mail_guid = detail.mail_guid; params.mail_guid = detail.mail_guid;
params.item_id = deleteSelected.item_id; params.item_id = deleteSelected.item_id;
params.parrent_count = deleteSelected.count; params.parrent_count = deleteSelected.count;
params.count = itemUpdateCount; params.count = count;
result = await UserMailItemDelete(token, params); await withLoading(async () => {
if(result.result === "SUCCESS"){ return await UserMailItemDelete(token, params);
if(deleteSelected.count <= itemUpdateCount){ }).then(data => {
const item_idx = detail.item_list.findIndex(item => item.item_id === deleteSelected.item_id); if(data.result === "SUCCESS") {
if(item_idx >= 0) { if(deleteSelected.count <= count){
detail.item_list.splice(item_idx, 1); const item_idx = detail.item_list.findIndex(item => item.item_id === params.item_id);
if(item_idx >= 0) {
detail.item_list.splice(item_idx, 1);
}
}else{
deleteSelected.count = deleteSelected.count - count;
} }
showToast('DEL_ITEM_COMPLETE', {type: alertTypes.success});
}else{ }else{
deleteSelected.count = deleteSelected.count - itemUpdateCount; showToast(data.data.message, {type: alertTypes.error});
} }
} }).catch(e => {
handleModalClose('deleteItem'); showToast(e, {type: alertTypes.error});
handleModalView('deleteItemComplete'); }).finally(() => {
break; handleModalClose('deleteItem');
case "deleteComplete": fetchPage(pagination.currentPage);
handleModalClose('deleteComplete'); });
handleModalClose('detail');
// const idx = dataList.mail_list.findIndex(mail => mail.mail_guid === detail.mail_guid);
// if(idx >= 0) {
// dataList.mail_list.splice(idx, 1);
// }
fetchData(option);
break;
case "deleteItemComplete":
handleModalClose('deleteItemComplete');
break; break;
} }
} }
const ConfirmChild = () => {
return(
<InputItem>
<p>{t('DEL_COUNT_CONFIRM', {count: deleteSelected.count})}</p>
<TextInput placeholder="수량" type="number" value={itemUpdateCount} onChange={e => handleItemCount(e)} width="200px" />
</InputItem>
);
}
return ( return (
loading ? <TableSkeleton count={10}/> : loading ? <TableSkeleton count={10}/> :
<> <>
<SelectWrapper> <SelectContainer>
<SelectInput <SelectInput
value={option} value={option}
onChange={e => { onChange={e => {
setOption(e.target.value); setOption(e.target.value);
}}> }}>
<option value="RECEIVE">받은 우편</option> {opMailType.map((data, index) => (
<option value="SEND">보낸 우편</option> <option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput> </SelectInput>
</SelectWrapper>
<DynamoPagination
pagination={pagination}
onNextPage={goToNextPage}
onPrevPage={goToPrevPage}
/>
</SelectContainer>
{option === 'RECEIVE' && ( {option === 'RECEIVE' && (
<> <>
{/* <CheckWrapper>
<CheckBox id="viewDelReceiveMail" label="삭제 우편 보기" />
</CheckWrapper> */}
<UserTableWrapper> <UserTableWrapper>
<UserDefaultTable> <UserDefaultTable>
<thead> <thead>
@@ -191,12 +216,10 @@ const UserMailInfo = ({ userInfo }) => {
<th width="100">첨부 아이템</th> <th width="100">첨부 아이템</th>
<th width="80">수령</th> <th width="80">수령</th>
<th width="80">시스템 우편</th> <th width="80">시스템 우편</th>
{/* <th width="170">수령 일자</th> */}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{dataList.mail_list && {dataList?.mail_list?.map((mail, idx) => {
dataList.mail_list.map((mail, idx) => {
return ( return (
<tr key={idx}> <tr key={idx}>
<td>{convertKTC(mail.create_time)}</td> <td>{convertKTC(mail.create_time)}</td>
@@ -211,13 +234,10 @@ const UserMailInfo = ({ userInfo }) => {
{mail.title} {mail.title}
</MailLink> </MailLink>
</td> </td>
<td>{mail.status === true ? '확인' : '미확인'}</td> <td>{opReadType.find(type => type.value === mail.status).name}</td>
<td>{mail.item_list.length > 0 ? 'Y' : 'N'}</td> <td>{opYNType.find(type => type.value === (mail.item_list.length > 0)).name}</td>
<td>{mail.is_get_item === true ? '수령함' : '미수령함'}</td> <td>{opPickupType.find(type => type.value === mail.is_get_item).name}</td>
<td>{mail.is_system_mail === true ? 'Y' : 'N'}</td> <td>{opYNType.find(type => type.value === mail.is_system_mail).name}</td>
{/* <td>
{mail.is_get_item_dt && String(new Date(new Date(mail.is_get_item_dt).setHours(new Date(mail.is_get_item_dt).getHours() + 9)).toLocaleString())}
</td> */}
</tr> </tr>
); );
})} })}
@@ -228,9 +248,6 @@ const UserMailInfo = ({ userInfo }) => {
)} )}
{option === 'SEND' && ( {option === 'SEND' && (
<> <>
{/* <CheckWrapper>
<CheckBox id="viewDelSendMail" label="삭제 우편 보기" />
</CheckWrapper> */}
<UserTableWrapper> <UserTableWrapper>
<UserDefaultTable> <UserDefaultTable>
<thead> <thead>
@@ -242,7 +259,7 @@ const UserMailInfo = ({ userInfo }) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{dataList.mail_list && {dataList && dataList.mail_list &&
dataList.mail_list.map((mail, idx) => { dataList.mail_list.map((mail, idx) => {
return ( return (
<tr key={idx}> <tr key={idx}>
@@ -267,6 +284,7 @@ const UserMailInfo = ({ userInfo }) => {
</UserTableWrapper> </UserTableWrapper>
</> </>
)} )}
{/*상세*/} {/*상세*/}
<MailDetailModal <MailDetailModal
mailModal={modalState.detailModal} mailModal={modalState.detailModal}
@@ -277,32 +295,19 @@ const UserMailInfo = ({ userInfo }) => {
handleItemDelete={(param) => handleModalSubmit('deleteItem', param)} handleItemDelete={(param) => handleModalSubmit('deleteItem', param)}
authDelete={authDelete} authDelete={authDelete}
/> />
{/*메일 삭제 모달*/}
<ConfirmModal
modalText={t('USER_MAIL_DEL_CONFIRM')}
view={modalState.deleteSubmitModal}
handleSubmit={() => handleModalSubmit('deleteSubmit')}
handleCancel={() => handleModalClose('deleteSubmit')}
handleClose={() => handleModalClose('deleteSubmit')}
/>
<CompletedModal
view={modalState.deleteCompleteModal}
modalText={t('DEL_COMPLETE')}
handleComplete={() => handleModalSubmit('deleteComplete')}
/>
{/*메일 아이템 삭제 모달*/} {/*메일 아이템 삭제 모달*/}
<CustomConfirmModal <CustomConfirmModal
ChildView={ConfirmChild} ChildView={() => (
<ConfirmChild
maxCount={deleteSelected.count}
/>
)}
view={modalState.deleteItemModal} view={modalState.deleteItemModal}
handleSubmit={() => handleModalSubmit('deleteItemSubmit')} handleSubmit={() => handleModalSubmit('deleteItemSubmit')}
handleCancel={() => handleModalClose('deleteItem')} handleCancel={() => handleModalClose('deleteItem')}
handleClose={() => handleModalClose('deleteItem')} handleClose={() => handleModalClose('deleteItem')}
/> />
<CompletedModal
view={modalState.deleteItemCompleteModal}
modalText={t('DEL_ITEM_COMPLETE')}
handleComplete={() => handleModalSubmit('deleteItemComplete')}
/>
</> </>
); );
}; };
@@ -365,22 +370,7 @@ const UserTableWrapper = styled.div`
const MailLink = styled.div` const MailLink = styled.div`
color: #61a2d0; color: #61a2d0;
text-decoration: underline; text-decoration: underline;
`; cursor: pointer;
const SelectWrapper = styled.div`
select {
height: 30px;
}
margin-bottom: 10px;
`;
const CheckWrapper = styled.div`
text-align: right;
padding: 10px;
margin-top: -40px;
height: 30px;
margin-bottom: 10px;
font-size: 14px;
font-weight: 700;
`; `;
const InputItem = styled.div` const InputItem = styled.div`
@@ -396,3 +386,14 @@ const InputItem = styled.div`
padding: 10px 20px; padding: 10px 20px;
} }
`; `;
const SelectContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
select {
height: 30px;
}
`;

View File

@@ -1,31 +1,38 @@
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(() => {
fetchData(); if(userInfo && Object.keys(userInfo).length > 0) {
fetchData();
}
}, []); }, []);
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 &&
@@ -34,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>
@@ -49,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>

View File

@@ -3,18 +3,27 @@ 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(() => {
fetchData(); if(userInfo && Object.keys(userInfo).length > 0) {
fetchData();
}
}, []); }, []);
const fetchData = async () => { const fetchData = async () => {
@@ -28,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 /> :
<> <>
@@ -57,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>
@@ -70,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} />
</> </>
); );
}; };

View File

@@ -1,57 +0,0 @@
import { styled } from 'styled-components';
import Button from '../../components/common/button/Button';
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper } from '../../styles/Components';
const UserSearchBar = () => {
return (
<>
<FormWrapper>
<SearchbarStyle>
<SearchItem>
<InputLabel>유저 조회</InputLabel>
<TextInput type="text" width="300px" placeholder="조회 대상 유저의 GUID를 입력하세요." />
</SearchItem>
<BtnWrapper $gap="8px">
<Button theme="reset" />
<Button
theme="primary"
text="검색"
handleClick={e => {
e.preventDefault();
}}
/>
</BtnWrapper>
</SearchbarStyle>
</FormWrapper>
</>
);
};
export default UserSearchBar;
const SearchbarStyle = styled.div`
width: 100%;
display: flex;
flex-wrap: wrap;
gap: 20px 0;
font-size: 14px;
padding: 20px;
border-top: 1px solid #000;
border-bottom: 1px solid #000;
margin-bottom: 40px;
`;
const SearchItem = styled.div`
display: flex;
align-items: center;
gap: 20px;
margin-right: 50px;
${TextInput}, ${SelectInput} {
height: 35px;
}
${TextInput} {
padding: 0 10px;
max-width: 400px;
}
`;

View 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;

View File

@@ -4,12 +4,14 @@ 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);
useEffect(() => { useEffect(() => {
fetchData(); if(userInfo && Object.keys(userInfo).length > 0) {
fetchData();
}
}, []); }, []);
const fetchData = async () => { const fetchData = async () => {
@@ -60,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;

View File

@@ -9,7 +9,9 @@ const UserToolInfo = ({ userInfo }) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
fetchData(); if(userInfo && Object.keys(userInfo).length > 0) {
fetchData();
}
}, []); }, []);
useEffect(() => { useEffect(() => {

View File

@@ -1,312 +1,279 @@
import { useEffect, useState } from 'react'; import React, { Fragment, useMemo, useRef, useState } from 'react';
import { styled } from 'styled-components';
import Button from '../../components/common/button/Button';
import { CurrencyIndexExport, CurrencyIndexView } from '../../apis'; import {
import { SelectInput, TableStyle, TableInfo, ListOption, InputLabel } from '../../styles/Components'; TableStyle,
FormWrapper,
TableWrapper, CircularProgressWrapper, TotalRow,
} from '../../styles/Components';
import CreditSeacrhBar from '../../components/IndexManage/CreditSearchBar'; import { useCurrencyIndexSearch } from '../searchBar';
import { uniqBy } from 'lodash'; import { Button, TopButton, ViewTableInfo } from '../common';
import { sumBy } from 'lodash'; import { TableSkeleton } from '../Skeleton/TableSkeleton';
import { numberFormatter } from '../../utils';
import {STORAGE_GAME_LOG_CURRENCY_SEARCH, } from '../../assets/data/adminConstants';
import ExcelExportButton from '../common/button/ExcelExportButton';
import CircularProgress from '../common/CircularProgress';
import { useTranslation } from 'react-i18next';
import CurrencyUserIndexSearchBar from '../searchBar/CurrencyUserIndexSearchBar';
import { useNavigate } from 'react-router-dom';
import { AnimatedPageWrapper } from '../common/Layout';
const CreditContent = () => { const CreditContent = () => {
const { t } = useTranslation();
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
let d = new Date(); const navigate = useNavigate();
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0)); const tableRef = useRef(null);
const END_DATE = new Date(); const [downloadState, setDownloadState] = useState({
const CURRENCY_LIST = [ loading: false,
{ "key": "Gold", "name": "골드" }, progress: 0
{ "key": "Sapphire", "name": "사파이어" }, });
{ "key": "Calium", "name": "칼리움" },
{ "key": "Onyxium", "name": "오닉시움" }
];
const [sendDate, setSendDate] = useState(START_DATE); const {
const [finishDate, setFinishDate] = useState(END_DATE); searchParams,
const [currencyType, setCurrencyType] = useState('Gold'); loading: dataLoading,
const [currencyText, setCurrencyText] = useState('골드'); data: dataList,
handleSearch,
handleReset,
updateSearchParams
} = useCurrencyIndexSearch(token);
const [dataList, setDataList] = useState([]); const tableHeaders = useMemo(() => {
const [routeData, setRouteData] = useState([]); return [
// 기본 컬럼 (rowSpan=2)
{ id: 'logDay', label: '일자', width: '100px', rowSpan: 2 },
{ id: 'accountId', label: 'account ID', width: '80px', rowSpan: 2 },
{ id: 'userGuid', label: 'GUID', width: '200px', rowSpan: 2 },
{ id: 'userNickname', label: '아바타명', width: '150px', rowSpan: 2 },
// 획득량 그룹 헤더 (첫 번째 행에만 표시)
{ id: 'acquired', label: '획득', width: '400px', colSpan: 5, groupHeader: true },
// 획득량 컬럼 (두 번째 행에만 표시)
{ id: 'sapphireAcquired', label: '사파이어', width: '80px', groupRow: true },
{ id: 'goldAcquired', label: '골드', width: '80px', groupRow: true },
{ id: 'caliumAcquired', label: '칼리움', width: '80px', groupRow: true },
{ id: 'beamAcquired', label: 'BEAM', width: '80px', groupRow: true },
{ id: 'rubyAcquired', label: '루비', width: '80px', groupRow: true },
// 소모량 그룹 헤더 (첫 번째 행에만 표시)
{ id: 'consumed', label: '소모', width: '400px', colSpan: 5, groupHeader: true },
// 소모량 컬럼 (두 번째 행에만 표시)
{ 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 }
];
}, []);
useEffect(() => { const totals = useMemo(() => {
fetchData(sendDate, finishDate, currencyType); if (!dataList?.currency_list?.length) return null;
}, [currencyType]);
const fetchData = async (startDate, endDate) => { return dataList.currency_list.reduce((acc, item) => {
const newStartDate = new Date(startDate); return {
const newEndDate = new Date(endDate); sapphireAcquired: (acc.sapphireAcquired || 0) + (item.sapphireAcquired || 0),
sapphireConsumed: (acc.sapphireConsumed || 0) + (item.sapphireConsumed || 0),
goldAcquired: (acc.goldAcquired || 0) + (item.goldAcquired || 0),
goldConsumed: (acc.goldConsumed || 0) + (item.goldConsumed || 0),
caliumAcquired: (acc.caliumAcquired || 0) + (item.caliumAcquired || 0),
caliumConsumed: (acc.caliumConsumed || 0) + (item.caliumConsumed || 0),
beamAcquired: (acc.beamAcquired || 0) + (item.beamAcquired || 0),
beamConsumed: (acc.beamConsumed || 0) + (item.beamConsumed || 0),
rubyAcquired: (acc.rubyAcquired || 0) + (item.rubyAcquired || 0),
rubyConsumed: (acc.rubyConsumed || 0) + (item.rubyConsumed || 0),
sapphireNet: (acc.sapphireNet || 0) + (item.sapphireNet || 0),
goldNet: (acc.goldNet || 0) + (item.goldNet || 0),
caliumNet: (acc.caliumNet || 0) + (item.caliumNet || 0),
beamNet: (acc.beamNet || 0) + (item.beamNet || 0),
rubyNet: (acc.rubyNet || 0) + (item.rubyNet || 0),
totalCurrencies: (acc.totalCurrencies || 0) + (item.totalCurrencies || 0),
};
}, {});
}, [dataList?.currency_list]);
const startDateToLocal = const handleModalSubmit = async (type, param = null) => {
newStartDate.getFullYear() + switch (type) {
'-' + case "detail":
(newStartDate.getMonth() + 1 < 9 ? '0' + (newStartDate.getMonth() + 1) : newStartDate.getMonth() + 1) + const params = {
'-' + tab: "CURRENCY",
(newStartDate.getDate() < 9 ? '0' + newStartDate.getDate() : newStartDate.getDate()); start_dt: (() => {
const date = new Date(param.logDay);
return date;
})(),
end_dt: (() => {
const date = new Date(param.logDay);
date.setDate(date.getDate() + 1);
return date;
})(),
guid: param.userGuid
};
const endDateToLocal = // 복사한 데이터를 세션 스토리지에 저장
newEndDate.getFullYear() + sessionStorage.setItem(STORAGE_GAME_LOG_CURRENCY_SEARCH, JSON.stringify(params));
'-' + navigate('/datamanage/gamelogview');
(newEndDate.getMonth() + 1 < 9 ? '0' + (newEndDate.getMonth() + 1) : newEndDate.getMonth() + 1) + break;
'-' + }
(newEndDate.getDate() < 9 ? '0' + newEndDate.getDate() : newEndDate.getDate()); }
setDataList(await CurrencyIndexView(token, startDateToLocal, endDateToLocal, currencyType));
setSendDate(startDateToLocal);
setFinishDate(endDateToLocal);
setRoutArray(await CurrencyIndexView(token, startDateToLocal, endDateToLocal, currencyType));
};
const handleCurrencyChange = e => {
let value = e.target.value;
setCurrencyType(value);
CURRENCY_LIST.filter(data => data.key === value).map(data => {
setCurrencyText(data.name);
});
};
//route data
const setRoutArray = async (data) => {
let routeArray = [];
let routeAcqArr = [];
let routeConArr = [];
//생산량 route
data.list && data.list[0].daily_data.filter(routeData => routeData.delta_type === 'ACQUIRE').map(routeData => {
routeData.data.map(routeData => {
if(!routeAcqArr.includes(routeData.route) ){
routeAcqArr.push(routeData.route);
}
})
});
routeArray.ACQUIRE = routeAcqArr;
//소진량 route
data.list && data.list[0].daily_data.filter(routeData => routeData.delta_type === 'CONSUME').map(routeData => {
routeData.data.map(routeData => {
if(!routeConArr.includes(routeData.route) ){
routeConArr.push(routeData.route);
}
})
});
routeArray.CONSUME = routeConArr;
setRouteData(routeArray);
};
// 엑셀 다운로드
const handleXlsxExport = () => {
const fileName = 'Caliverse_Credit_Index.xlsx';
CurrencyIndexExport(token, fileName, sendDate, finishDate, currencyType);
};
return ( return (
<> <AnimatedPageWrapper>
<FormWrapper>
<CurrencyUserIndexSearchBar
searchParams={searchParams}
onSearch={(newParams, executeSearch = true) => {
if (executeSearch) {
handleSearch(newParams);
} else {
updateSearchParams(newParams);
}
}}
onReset={handleReset}
/>
</FormWrapper>
<ViewTableInfo >
<ExcelExportButton
functionName="GameCurrencyLogExport"
params={searchParams}
fileName={t('FILE_CURRENCY_INDEX')}
onLoadingChange={setDownloadState}
dataSize={dataList?.length}
/>
{downloadState.loading && (
<CircularProgressWrapper>
<CircularProgress
progress={downloadState.progress}
size={36}
progressColor="#4A90E2"
/>
</CircularProgressWrapper>
)}
</ViewTableInfo>
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
<>
<TableWrapper>
<TableStyle ref={tableRef}>
<thead>
<tr>
{/* 첫 번째 행 - 기본 컬럼 + 그룹 헤더 + rowSpan=2 컬럼 */}
{tableHeaders.map(header => {
if (header.groupRow) return null; // 두 번째 행의 컬럼은 첫 번째 행에서 건너뜀
<CreditSeacrhBar fetchData={fetchData} /> return (
<TableInfo2> <th
<SelectInput onChange={handleCurrencyChange}> key={header.id}
{CURRENCY_LIST.map((item, index) => ( width={header.width}
<option value={item.key} key={index}> rowSpan={header.rowSpan}
{item.name} colSpan={header.colSpan}
</option> >
))} {header.label}
</SelectInput> </th>
<ListOption> );
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} /> })}
</ListOption> </tr>
</TableInfo2> <tr>
<TableWrapper> {/* 두 번째 행 - 그룹 내 하위 컬럼만 */}
<EconomicTable> {tableHeaders.map(header => {
<thead> if (!header.groupRow) return null; // 첫 번째 행이나 rowSpan=2 컬럼은 두 번째 행에서 건너뜀
<tr>
<th colSpan="2" className="text-center" width="300"> return (
Product <th key={header.id} width={header.width}>
</th> {header.label}
{dataList.list && uniqBy(dataList.list, 'date').map(data => </th>
<th width="160" key={data.date}>{data.date}</th> );
})}
</tr>
</thead>
<tbody>
{totals && (
<TotalRow>
<td colSpan="4">합계</td>
{/* 획득 그룹 합계 */}
<td>{numberFormatter.formatCurrency(totals.sapphireAcquired)}</td>
<td>{numberFormatter.formatCurrency(totals.goldAcquired)}</td>
<td>{numberFormatter.formatCurrency(totals.caliumAcquired)}</td>
<td>{numberFormatter.formatCurrency(totals.beamAcquired)}</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.sapphireNet)}</td>
<td>{numberFormatter.formatCurrency(totals.goldNet)}</td>
<td>{numberFormatter.formatCurrency(totals.caliumNet)}</td>
<td>{numberFormatter.formatCurrency(totals.beamNet)}</td>
<td>{numberFormatter.formatCurrency(totals.rubyNet)}</td>
<td>{totals.totalCurrencies}</td>
<td>-</td>
</TotalRow>
)} )}
</tr> {dataList?.currency_list?.map((item, index) => (
</thead> <Fragment key={index}>
<tbody> <tr>
<tr> {/* 기본 정보 */}
<TableTitle colSpan="2">(Total) {currencyText} 생산량</TableTitle> <td>{item.logDay}</td>
{dataList.list && <td>{item.accountId}</td>
dataList.list.map((data) => <td>{item.userGuid}</td>
(data.total).filter(totalData => data.date === totalData.date && totalData.delta_type === 'ACQUIRE') <td>{item.userNickname}</td>
.map((totalData, index) => (
<TableData key={index} {/* 획득 그룹 */}
$state={totalData.dif !== "" && totalData.dif !== "Infinity" ? "danger" : ""}> <td>{numberFormatter.formatCurrency(item.sapphireAcquired)}</td>
{totalData.quantity} <td>{numberFormatter.formatCurrency(item.goldAcquired)}</td>
{ <td>{numberFormatter.formatCurrency(item.caliumAcquired)}</td>
totalData.dif !== "" && totalData.dif !== "Infinity" <td>{numberFormatter.formatCurrency(item.beamAcquired)}</td>
? (<span>({totalData.dif})</span>) <td>{numberFormatter.formatCurrency(item.rubyAcquired)}</td>
: ("")
} {/* 소모 그룹 */}
</TableData> <td>{numberFormatter.formatCurrency(item.sapphireConsumed)}</td>
) <td>{numberFormatter.formatCurrency(item.goldConsumed)}</td>
)) <td>{numberFormatter.formatCurrency(item.caliumConsumed)}</td>
} <td>{numberFormatter.formatCurrency(item.beamConsumed)}</td>
</tr> <td>{numberFormatter.formatCurrency(item.rubyConsumed)}</td>
<tr>
<TableTitle colSpan="2">(Total) {currencyText} 소진량</TableTitle> {/* 계 */}
{dataList.list && <td>{numberFormatter.formatCurrency(item.sapphireNet)}</td>
dataList.list.map((data) => <td>{numberFormatter.formatCurrency(item.goldNet)}</td>
(data.total).filter(totalData => data.date === totalData.date && totalData.delta_type === 'CONSUME') <td>{numberFormatter.formatCurrency(item.caliumNet)}</td>
.map((totalData, index) => ( <td>{numberFormatter.formatCurrency(item.beamNet)}</td>
<TableData key={index} <td>{numberFormatter.formatCurrency(item.rubyNet)}</td>
$state={totalData.dif !== "" && totalData.dif !== "Infinity" ? "danger" : ""}>
{totalData.quantity} <td>{item.totalCurrencies}</td>
{ <td>
totalData.dif !== "" && totalData.dif !== "Infinity" <Button theme="line" text="상세보기"
? (<span>({totalData.dif})</span>) handleClick={e => handleModalSubmit('detail', item)} />
: ("") </td>
} </tr>
</TableData> </Fragment>
) ))}
)) </tbody>
} </TableStyle>
</tr> </TableWrapper>
<tr> <TopButton />
<TableTitle colSpan="2">(Total) {currencyText} 보유량</TableTitle> </>
{dataList.list && }
dataList.list.map((data, index) => ( </AnimatedPageWrapper>
<TableData key={index}>
{sumBy(
data.total.filter(totalData => data.date === totalData.date && totalData.delta_type === 'ACQUIRE'),
'quantity',
) -
sumBy(
data.total.filter(totalData => data.date === totalData.date && totalData.delta_type === 'CONSUME'),
'quantity',
)}
</TableData>
))
}
</tr>
{/* 획득 GET */}
{
routeData.ACQUIRE && routeData.ACQUIRE.map((route, index) => (
<tr key={index}>
<TableTitle>GET</TableTitle>
<TableTitle>{route}</TableTitle>
{dataList.list &&
dataList.list.map((data) =>
data.daily_data.filter(dailyData => data.date === dailyData.date && dailyData.delta_type === 'ACQUIRE')
.map(dailyData => (dailyData.data).filter(routeData => data.date === routeData.date && routeData.route === route)
.map((routeData, i) => (
<TableData key={i} data={routeData.date}>{routeData.quantity}</TableData>
)))
)
}
</tr>
))
}
{/* 소진 USE CONSUME */}
{
routeData.CONSUME && routeData.CONSUME.map((route, index) => (
<tr key={index}>
<TableTitle>USE</TableTitle>
<TableTitle>{route}</TableTitle>
{dataList.list &&
dataList.list.map((data) =>
data.daily_data.filter(dailyData => data.date === dailyData.date && dailyData.delta_type === 'CONSUME')
.map(dailyData => (dailyData.data).filter(routeData => data.date === routeData.date && routeData.route === route)
.map((routeData, i) => (
<TableData key={i} data={routeData.date}>{routeData.quantity}</TableData>
)))
)
}
</tr>
))
}
</tbody>
</EconomicTable>
</TableWrapper>
</>
); );
}; };
export default CreditContent; export default CreditContent;
const TableWrapper = styled.div`
width: 100%;
min-width: 680px;
overflow: auto;
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-thumb {
background: #666666;
}
&::-webkit-scrollbar-track {
background: #d9d9d9;
}
${TableStyle} {
width: 100%;
min-width: 900px;
th {
&.cell-nru {
background: #f0f0f0;
border-left: 1px solid #aaa;
border-right: 1px solid #aaa;
}
}
td {
&.blank {
background: #f9f9f9;
}
&.cell-nru {
background: #fafafa;
border-left: 1px solid #aaa;
border-right: 1px solid #aaa;
}
}
}
`;
const TableInfo2 = styled(TableInfo)`
justify-content: space-between;
${InputLabel} {
font-size: 12px;
}
${SelectInput} {
width: auto;
min-width: 100px;
height: 24px;
}
`;
const TableDate = styled.th`
color: ${props => (props.$state === 'danger' ? '#d60000' : '#2c2c2c')};
`;
const TableData = styled.td`
background: ${props => (props.$state === 'danger' ? '#d60000' : props.$state === 'blank' ? '#F9F9F9' : 'transparent')};
color: ${props => (props.$state === 'danger' ? '#fff' : '#2c2c2c')};
`;
const perData = styled.span`
display: ${props => (props.$view === 'hidden' ? 'none' : 'block')};
`;
const TableTitle = styled.td`
text-align: center;
`;
const EconomicTable = styled(TableStyle)`
${TableData} {
text-align: left;
}
tbody {
tr:nth-child(1),
tr:nth-child(2),
tr:nth-child(3) {
background: #f5fcff;
}
}
`;

View 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;

View 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;

View 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;

View File

@@ -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;

View 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;
`;

View File

@@ -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);
}); });
}; };

View File

@@ -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;

View File

@@ -1,7 +1,7 @@
import { styled } from 'styled-components'; import { styled } from 'styled-components';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import DecoSearchBar from '../../components/IndexManage/DecoSearchBar'; import DecoSearchBar from '../searchBar/DecoSearchBar';
import Button from '../../components/common/button/Button'; import Button from '../../components/common/button/Button';
import { TableStyle, TableInfo, ListOption } from '../../styles/Components'; import { TableStyle, TableInfo, ListOption } from '../../styles/Components';

View File

@@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
import { TableStyle, TableInfo, ListOption } from '../../styles/Components'; import { TableStyle, TableInfo, ListOption } from '../../styles/Components';
import Button from '../../components/common/button/Button'; import Button from '../../components/common/button/Button';
import InstanceSearchBar from '../../components/IndexManage/InstanceSearchBar'; import InstanceSearchBar from '../searchBar/InstanceSearchBar';
import { InstanceIndexExport, InstanceIndexView } from '../../apis'; import { InstanceIndexExport, InstanceIndexView } from '../../apis';
const InstanceContent = () => { const InstanceContent = () => {

View 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;

View 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;

View 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;

View File

@@ -5,7 +5,7 @@ import Button from '../../components/common/button/Button';
import { SelectInput, TableStyle, TableInfo, ListOption, InputLabel, InputGroup } from '../../styles/Components'; import { SelectInput, TableStyle, TableInfo, ListOption, InputLabel, InputGroup } from '../../styles/Components';
import ItemSearchBar from '../../components/IndexManage/ItemSearchBar'; import ItemSearchBar from '../searchBar/ItemSearchBar';
import { ItemIndexExport, ItemIndexView } from '../../apis'; import { ItemIndexExport, ItemIndexView } from '../../apis';
const ItemContent = () => { const ItemContent = () => {

View File

@@ -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;

View File

@@ -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>
); );
}; };

View File

@@ -1,166 +0,0 @@
import { useEffect, useState } from 'react';
import { styled } from 'styled-components';
import Button from '../../components/common/button/Button';
import DatePickerComponent from '../common/Date/DatePickerComponent';
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper, AlertText } from '../../styles/Components';
const RetentionSearchBar = ({ resultData, setResultData, handleSearch, fetchData, setRetention, setExcelBtn }) => {
// 초기 날짜 세팅
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 [errorMessage, setErrorMessage] = useState('');
const [period, setPeriod] = useState(0);
// resultData에 임의 날짜 넣어주기
useEffect(() => {
setResultData({
send_dt: START_DATE,
end_dt: '',
retention: 0,
});
}, []);
// 발송 날짜 세팅 로직
const handleSelectedDate = data => {
const sendDate = new Date(data);
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate());
const resultEndDate = new Date(resultSendData);
resultEndDate.setDate(resultEndDate.getDate() + Number(resultData.retention));
setResultData({ ...resultData, send_dt: resultSendData, end_dt: resultEndDate });
};
// // 발송 날짜 세팅 로직
// const handleEndDate = data => {
// const endDate = new Date(data);
// const resultSendData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
// setResultData({ ...resultData, end_dt: resultSendData });
// };
// Retention 세팅 로직
const handleRetention = e => {
const value = e.target.value;
const resultEndDate = new Date(resultData.send_dt);
resultEndDate.setDate(resultEndDate.getDate() + Number(value));
setResultData({ ...resultData, end_dt: resultEndDate, retention: value });
setPeriod(value);
};
//Retention 범위 선택 후 disable 처리 로직
const handleSearchBtn = e => {
e.preventDefault();
if(period == 0) {
setErrorMessage("필수값을 선택하세요.");
return false;
} else {
setErrorMessage("");
setExcelBtn(false); //활성화
handleSearch(resultData.send_dt, resultData.end_dt);
}
}
const handleReset = e => {
e.preventDefault();
setResultData({ send_dt: START_DATE, end_dt: '', retention: 0 });
setRetention(1);
setErrorMessage("");
setPeriod(1);
setExcelBtn(true); //비활성화
fetchData(START_DATE, END_DATE);
};
return (
<>
<FormWrapper>
<SearchbarStyle>
<SearchItem>
<InputLabel>집계 기준일</InputLabel>
<InputGroup>
<DatePickerWrapper>
<DatePickerComponent
name="시작 일자" selectedDate={resultData.send_dt}
handleSelectedDate={data => handleSelectedDate(data)}
maxDate={new Date()} />
<span>~</span>
<DatePickerComponent
name="종료 일자"
selectedDate={resultData.end_dt}
maxDate={new Date()}
readOnly={true}
disabled={true}
type="retention" />
</DatePickerWrapper>
</InputGroup>
</SearchItem>
<SearchItem>
<InputLabel>Retention 범위</InputLabel>
<SelectInput
onChange={e => handleRetention(e)} value={resultData.retention}>
<option value={0}>선택</option>
<option value={1}>D+1</option>
<option value={7}>D+7</option>
<option value={30}>D+30</option>
</SelectInput>
</SearchItem>
{/* 기획 보류 */}
{/* <SearchItem>
<InputLabel>조회 국가</InputLabel>
<SelectInput>
<option value="">ALL</option>
<option value="">KR</option>
<option value="">EN</option>
<option value="">JP</option>
<option value="">TH</option>
</SelectInput>
</SearchItem> */}
<BtnWrapper $gap="8px">
<Button theme="reset" handleClick={handleReset} />
<Button
theme="search"
text="검색"
handleClick={handleSearchBtn}
/>
<AlertText>{errorMessage}</AlertText>
</BtnWrapper>
</SearchbarStyle>
</FormWrapper>
</>
);
};
export default RetentionSearchBar;
const SearchbarStyle = styled.div`
width: 100%;
display: flex;
flex-wrap: wrap;
gap: 20px 0;
font-size: 14px;
padding: 20px;
border-top: 1px solid #000;
border-bottom: 1px solid #000;
margin-bottom: 40px;
`;
const SearchItem = styled.div`
display: flex;
align-items: center;
gap: 20px;
margin-right: 50px;
${TextInput}, ${SelectInput} {
height: 35px;
}
${TextInput} {
padding: 0 10px;
max-width: 400px;
}
`;

View File

@@ -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;

View File

@@ -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>
</>
); );
}; };

View File

@@ -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 '../../components/IndexManage/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;
}
}
`;

Some files were not shown because too many files have changed in this diff Show More