Compare commits

..

22 Commits

Author SHA1 Message Date
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
67 changed files with 4490 additions and 827 deletions

View File

@@ -9,11 +9,11 @@ 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, CryptView, GameLogView, UserView, BusinessLogView, } from './pages/DataManage';
import { import {
Board, Board,
Event, Event,
@@ -49,6 +49,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,9 +57,10 @@ 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="cryptview" element={<CryptView />} />
<Route path="businesslogview" element={<BusinessLogView />} />
</Route> </Route>
<Route path="/servicemanage"> <Route path="/servicemanage">
<Route path="board" element={<Board />} /> <Route path="board" element={<Board />} />

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

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(

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

@@ -0,0 +1,18 @@
//운영 정보 관리 - 로그 api 연결
import { Axios } from '../utils';
// 비즈니스 로그 조회
export const BusinessLogList = async (token, params) => {
try {
const res = await Axios.post(`/api/v1/log/generic/list`, params, {
headers: { Authorization: `Bearer ${token}` },
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('BusinessLogList Error', e);
}
}
};

View File

@@ -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 {
@@ -187,9 +201,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

@@ -18,6 +18,14 @@ export {
landAuctionStatus, landAuctionStatus,
landSearchType, landSearchType,
CurrencyType, CurrencyType,
languageType languageType,
opLandCategoryType,
opLandOwnedType,
opSuccessType,
opPickupType,
opReadType,
opYNType,
opUserSessionType,
opMailType,
} from './options' } from './options'
export {benItems, MinuteList, HourList, caliumRequestInitData, STATUS_STYLES, months} from './data' export {benItems, MinuteList, HourList, caliumRequestInitData, STATUS_STYLES, months} 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,43 @@ 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: true,
authLevel: adminAuthLevel.NONE
}, },
cryptview: { cryptview: {
title: '크립토 조회', title: '크립토 조회',
permissions: { permissions: {
read: authType.cryptoRead read: authType.cryptoRead
} },
view: true,
authLevel: adminAuthLevel.NONE
},
businesslogview: {
title: '비즈니스 로그 조회',
permissions: {
read: authType.businessLogRead
},
view: true,
authLevel: adminAuthLevel.NONE
} }
} }
}, },
@@ -95,7 +130,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 +140,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,7 +150,9 @@ 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: '신고내역',
@@ -119,7 +160,9 @@ export const menuConfig = {
read: authType.reportRead, read: authType.reportRead,
update: authType.reportUpdate, update: authType.reportUpdate,
delete: authType.reportDelete delete: authType.reportDelete
} },
view: true,
authLevel: adminAuthLevel.NONE
}, },
event: { event: {
title: '보상 이벤트 관리', title: '보상 이벤트 관리',
@@ -127,7 +170,9 @@ export const menuConfig = {
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 +180,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 +190,9 @@ 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
}, },
} }
} }

View File

@@ -149,6 +149,12 @@ export const userSearchType = [
{ value: 'NAME', name: '닉네임' }, { value: 'NAME', name: '닉네임' },
]; ];
export const userSearchType2 = [
{ value: 'GUID', name: 'GUID' },
{ value: 'NICKNAME', name: '닉네임' },
{ value: 'ACCOUNT', name: 'Account ID' },
];
export const landSearchType = [ export const landSearchType = [
{ value: 'ID', name: '랜드ID' }, { value: 'ID', name: '랜드ID' },
{ value: 'NAME', name: '랜드명' }, { value: 'NAME', name: '랜드명' },
@@ -206,3 +212,658 @@ export const eventSearchType = [
export const battleEventRoundCount = [1,2,3,4,8,12,16]; export const battleEventRoundCount = [1,2,3,4,8,12,16];
export const battleEventHotTime = [1,2,3,4,5,6,7,8]; export const battleEventHotTime = [1,2,3,4,5,6,7,8];
export const opLandOwnedType = [
{ value: true, name: '가능' },
{ value: false, name: '불가능' },
];
export const opLandCategoryType = [
{ value: 'ALL', name: '전체' },
{ value: 'public', name: '공공 임대형' },
{ value: 'auction', name: '경매' },
{ value: 'event', name: '이벤트 지급' },
];
export const opLandInfoStatusType = [
{ value: 'ALL', name: '전체' },
{ value: 'NONE', name: '' },
{ value: 'OWNED', name: '지급 완료' },
{ value: 'OWNED_WAIT', name: '지급 예약' },
{ value: 'AUCTION_END', name: '경매 완료' },
{ value: 'AUCTION_WAIT', name: '경매 대기' },
{ value: 'AUCTION_RUNNING', name: '경매 진행' },
];
export const opInitDataType = [
{ value: 'None', name: '' },
{ value: 'LandAuction', name: '랜드경매' },
{ value: 'LandOwned', name: '랜드소유자' },
{ value: 'LandDesc', name: '랜드문구' },
];
export const opSuccessType = [
{ value: true, name: '성공' },
{ value: false, name: '실패' },
];
export const opUserSessionType = [
{ value: true, name: '접속중' },
{ value: false, name: '미접속' },
];
export const opYNType = [
{ value: true, name: 'Y' },
{ value: false, name: 'N' },
];
export const opReadType = [
{ value: true, name: '확인' },
{ value: false, name: '미확인' },
];
export const opPickupType = [
{ value: true, name: '수령' },
{ value: false, name: '미수령' },
];
export const opMailType = [
{ value: 'RECEIVE', name: '받은 우편' },
{ value: 'SEND', name: '보낸 우편' },
];
export const opInputType = [
{ value: 'String', name: '문자열' },
{ value: 'Number', name: '숫자' },
{ value: 'Boolean', name: '부울' },
];
// export const logAction = [
// { value: "None", name: "ALL" },
// { value: "AIChatDeleteCharacter", name: "NPC 삭제" },
// { value: "AIChatDeleteUser", name: "유저 삭제" },
// { value: "AIChatGetCharacter", name: "NPC 조회" },
// { value: "AIChatIncentiveMarking", name: "인센티브 획득 마킹" },
// { value: "AIChatIncentiveSearch", name: "인센티브 조회" },
// { value: "AIChatJwtIssue", name: "Jwt 토큰 발행" },
// { value: "AIChatJwtVerify", name: "Jwt 토큰 확인" },
// { value: "AIChatPointCharge", name: "포인트 충전" },
// { value: "AIChatPointChargeVerify", name: "포인트 충전 확인" },
// { value: "AIChatRegisterCharacter", name: "NPC 등록" },
// { value: "AIChatRegisterUser", name: "유저 등록" },
// { value: "AIChatUpdateCharacter", name: "NPC 정보 업데이트" },
// { value: "BanParty", name: "파티 추방" },
// { value: "BattleInstanceJoin", name: "배틀 인스턴스 조인" },
// { value: "BattleInstanceSnapshotCreate", name: "배틀 인스턴스 스냅샷 생성" },
// { value: "BattleInstanceSnapshotSave", name: "배틀 인스턴스 스냅샷 저장" },
// { value: "BattleObjectInteraction", name: "배틀 오브젝트 상호작용" },
// { value: "BattleObjectStateUpdate", name: "배틀 오브젝트 상태 변경" },
// { value: "BattlePodCombatOccupyReward", name: "포드 컴뱃 소유 보상" },
// { value: "BattleRoundStateUpdate", name: "배틀 라운드 스테이트 업데이트" },
// { value: "BattleUserDead", name: "유저 데드" },
// { value: "BattleUserRespawn", name: "배틀 리스폰" },
// { value: "BeaconAppearanceCustomize", name: "비컨 외형 커스터마이징" },
// { value: "BeaconCreate", name: "비컨 생성" },
// { value: "BeaconEdit", name: "비컨 편집" },
// { value: "BeaconSell", name: "비컨 매각" },
// { value: "BrokerApiAdmin", name: "BrokerApi 어드민 재화 지급" },
// { value: "BrokerApiPlanetAuth", name: "BrokerApi Planet 인증" },
// { value: "BrokerApiUserExchangeComplete", name: "BrokerApi 재화 교환 완료" },
// { value: "BrokerApiUserExchangeOrder", name: "BrokerApi 재화 교환 주문" },
// { value: "BrokerApiUserLogin", name: "BrokerApi 유저 로그인" },
// { value: "BuffAdd", name: "버프 추가" },
// { value: "BuffDelete", name: "버프 제거" },
// { value: "CaliumSyncEchoSystem", name: "칼리움 동기화 처리" },
// { value: "CancelFriendRequest", name: "친구요청 취소" },
// { value: "CartAdd", name: "장바구니 추가" },
// { value: "CartDelete", name: "장바구니 삭제" },
// { value: "CartPurchase", name: "장바구니 구매" },
// { value: "ChangeConvertCaliumInfo", name: "칼리움 정보 변환" },
// { value: "CharacterAppearanceCustomize", name: "캐리터 외형 커스터마이징" },
// { value: "CharacterAppearanceUpdate", name: "캐릭터 외형 갱신" },
// { value: "CharacterCreate", name: "캐릭터 생성(자동 생성)" },
// { value: "CharacterLoading", name: "캐릭터 로딩" },
// { value: "ChatChannel", name: "채널 채팅" },
// { value: "ChatNormal", name: "노말 채팅" },
// { value: "ChatNotice", name: "전서버 채팅" },
// { value: "ChatParty", name: "파티 채팅" },
// { value: "ChatWhisper", name: "귓속말 채팅" },
// { value: "CheatAllCraftFinish", name: "치트로 인한 모든 제작 시간 단축" },
// { value: "CheatCommandChangeNickName", name: "치트로 인한 캐릭터 명 변경" },
// { value: "CheatCommandCharacterInit", name: "치트로 인한 캐릭터 초기화" },
// { value: "CheatCommandClaimReset", name: "치트로 인한 클레임 리워드 리셋" },
// { value: "CheatCommandClaimUpdate", name: "치트로 인한 클레임 리워드 대기시간 단축" },
// { value: "CheatCommandCraftHelpInit", name: "치트로 인한 제작 헬프 초기화" },
// { value: "CheatCommandDeleteQuest", name: "치트로 인한 퀘스트 삭제" },
// { value: "CheatCommandGainLand", name: "치트로 인한 랜드 획득" },
// { value: "CheatCommandIncreaseExp", name: "치트로 인한 시즌 패스 경험치 증가" },
// { value: "CheatCommandItem", name: "치트로 인한 아이템 추가 삭제" },
// { value: "CheatCommandItemLevelUp", name: "치트로 인한 아이템 레벨 업" },
// { value: "CheatCommandLandAuctionBlindSet", name: "치트로 인한 랜드 경매 블라이인드 입찰 설정" },
// { value: "CheatCommandLandAuctionCanel", name: "치트로 인한 랜드 경매 취소" },
// { value: "CheatCommandLandAuctionReserve", name: "치트로 인한 랜드 경매 예약" },
// { value: "CheatCommandLandAuctionStart", name: "치트로 인한 랜드 경매 시작" },
// { value: "CheatCommandPackageSend", name: "치트로 인한 패키지 메일 전송" },
// { value: "CheatCommandQuestAccept", name: "치트로 인한 퀘스트 할당" },
// { value: "CheatCommandQuestComplete", name: "치트로 인한 퀘스트 완료" },
// { value: "CheatCommandRegisterCraftRecipe", name: "치트로 인한 레시피 등록" },
// { value: "CheatCommandResetAllQuest", name: "치트로 인한 퀘스트 리셋" },
// { value: "CheatCommandResetEscapePosition", name: "치트로 인한 탈출시간 리셋" },
// { value: "CheatCommandResetMailCount", name: "치트로 인한 메일 횟수 제한 초기화" },
// { value: "CheatCommandSeasonPassInit", name: "치트로 인한 시즌 패스 초기화" },
// { value: "CheatCommandSendMail", name: "치트로 인한 메일 발송" },
// { value: "CheatCommandShopProductInit", name: "치트로 인한 상점 품목 초기화" },
// { value: "CheatCommandShopProductRenewal", name: "치트로 인한 상점 갱신 전송" },
// { value: "ClaimReward", name: "클레임 리워드 이벤트 보상" },
// { value: "ConvertCalium", name: "칼리움 컨버터 변환" },
// { value: "ConvertExchangeCalium", name: "칼리움 교환소 변환" },
// { value: "CraftFinish", name: "제작 완료" },
// { value: "CraftHelp", name: "제작 도움" },
// { value: "CraftRecipeRegister", name: "제작 레시피 추가" },
// { value: "CraftStart", name: "제작 시작" },
// { value: "CraftStop", name: "제작 취소" },
// { value: "CreateCaliumContent", name: "칼리움 컨텐츠 생성" },
// { value: "CreateParty", name: "파티 생성" },
// { value: "CreatePartyInstance", name: "파티 던전 생성" },
// { value: "DailyQuestCheck", name: "데일리 퀘스트 체크" },
// { value: "DanceEntityStateEnd", name: "캐릭터 엔티티 스테이트 댄스 종료" },
// { value: "DanceEntityStateStart", name: "캐릭터 엔티티 스테이트 댄스 시작" },
// { value: "DeleteMyhome", name: "마이홈 삭제" },
// { value: "DestroyParty", name: "파티 파괴" },
// { value: "EndPartyVote", name: "파티 투표 종료" },
// { value: "EnterMyhome", name: "마이홈 입장" },
// { value: "EnterMyhomeEditRoom", name: "마이홈 에디트 룸 입장" },
// { value: "FailCaliumEchoSystem", name: "칼리움 에코시스템 실패" },
// { value: "FailCaliumStorageRollBack", name: "칼리움 컨버터 롤백 실패" },
// { value: "FarmingCancel", name: "파밍 취소" },
// { value: "FarmingComplete", name: "파밍 완료" },
// { value: "FarmingIncompletedReward", name: "파밍 미완료 보상" },
// { value: "FarmingStart", name: "파밍 시작" },
// { value: "FillupCalium", name: "칼리움 총량 누적" },
// { value: "FriendAdd", name: "친구추가" },
// { value: "FriendDelete", name: "친구삭세" },
// { value: "GainLandProfit", name: "랜드 수익 획득" },
// { value: "InviteParty", name: "파티 초대" },
// { value: "ItemBuy", name: "아이템 구매" },
// { value: "ItemDestroy", name: "아이템 제거" },
// { value: "ItemRandomBoxUse", name: "아이템 랜덤박스 사용" },
// { value: "ItemTattooChangeAttribute", name: "타투 아이템 속성변환" },
// { value: "ItemTattooLevelUp", name: "타투 아이템 강화" },
// { value: "ItemUse", name: "아이템 사용" },
// { value: "JoinInstance", name: "인스턴스 입장" },
// { value: "JoinParty", name: "파티 가입" },
// { value: "JoinPartyInstance", name: "파티 인스턴스 입장" },
// { value: "KickFriendsFromMyhome", name: "마이홈에서 친구 내쫒기" },
// { value: "LandAuctionActivity", name: "랜드 경매 활성화" },
// { value: "LandAuctionBid", name: "랜드 경매 입찰" },
// { value: "LandAuctionBidPriceRefund", name: "랜드 경매 입찰금 환급" },
// { value: "LandAuctionCheck", name: "랜드 경매 체크" },
// { value: "LeaveInstance", name: "인스턴스 퇴장" },
// { value: "LeaveParty", name: "파티 탈퇴" },
// { value: "LeavePartyInstance", name: "파티 인스턴스 퇴장" },
// { value: "LoginToGame", name: "게임 로그인" },
// { value: "LoginToGameSnapShot", name: "게임 로그인 스냅샷" },
// { value: "LoginToUserAuth", name: "계정 로그인" },
// { value: "MailAiChatIncentivePoint", name: "AI Chat 인센티브 우편 지급" },
// { value: "MailDestroy", name: "우편 삭제" },
// { value: "MailGetSystemMail", name: "시스템 우편 받기" },
// { value: "MailInitSendCount", name: "우편 보내기 기회 초기화" },
// { value: "MailRead", name: "우편 읽기" },
// { value: "MailSend", name: "우편 발송" },
// { value: "MailTaken", name: "우편 첨부 수령" },
// { value: "ModifyLandInfo", name: "랜드 정보 수정" },
// { value: "MoneyChange", name: "재화 변경" },
// { value: "ProductGive", name: "결제 상품 지급" },
// { value: "ProductOpenFailed", name: "결제 상품 오픈 실패" },
// { value: "ProductOpenSuccess", name: "결제 상품 오픈 성공" },
// { value: "QuestMailSend", name: "퀘스트 우편 발송" },
// { value: "QuestMainAbort", name: "퀘스트 메인 포기" },
// { value: "QuestMainAssignByDialogue", name: "대화를 통한 퀘스트 메인 수락" },
// { value: "QuestMainAssignForce", name: "퀘스트 메인 강제 수락" },
// { value: "QuestMainRefuse", name: "퀘스트 메인 수락 거절" },
// { value: "QuestMainRepeatTimeInit", name: "반복가능 퀘스트 리프레시 타임 초기화" },
// { value: "QuestMainRepeatTimeRefresh", name: "반복가능 퀘스트 리프레시 타임 갱신" },
// { value: "QuestMainReward", name: "퀘스트 메인 보상" },
// { value: "QuestMainTask", name: "퀘스트 메인 태스크관련" },
// { value: "QuestTaskUpdate", name: "퀘스트 태스크 업데이트" },
// { value: "RefuseFriendRequest", name: "친구요청 거절" },
// { value: "RenameFriendFolder", name: "친구 폴더면 수정" },
// { value: "RenameMyhome", name: "마이홈 이름 변경" },
// { value: "RenewalShopProducts", name: "사용자 요청에 의한 상픔 리스트 갱심" },
// { value: "RentFloor", name: "빌딩 층 임대" },
// { value: "ReplyInviteParty", name: "파티 초대 응답" },
// { value: "ReplySummonParty", name: "파티 맴버 소환 응답" },
// { value: "ReservationEnterToServer", name: "서버 이동 예약" },
// { value: "RewardProp", name: "리워드 프랍" },
// { value: "SaveMyhome", name: "마이홈 저장" },
// { value: "SeasonPassBuyCharged", name: "시즌 패스 유료 구입" },
// { value: "SeasonPassStartNew", name: "새로운 시즌 패스 시작" },
// { value: "SeasonPassTakeReward", name: "시즌 패스 보상 획득" },
// { value: "SendFriendRequest", name: "친구 신청" },
// { value: "ShopChangeProductTradingMeter", name: "상품 리스트 갱신" },
// { value: "ShopGetProductTradingMeter", name: "상품 리스트 조회" },
// { value: "ShopGetRePurchase", name: "판매한 상품 리스트 조회" },
// { value: "ShopPurchase", name: "상품 구매" },
// { value: "ShopRePurchase", name: "판매한 상품 재구매" },
// { value: "ShopSell", name: "상품 판매" },
// { value: "StageConcertStart", name: "콘서트 시작" },
// { value: "StageEnter", name: "스테이지 입장" },
// { value: "StageExit", name: "스테이지 퇴장" },
// { value: "StartPartyVote", name: "파티 투표 시작" },
// { value: "SummonParty", name: "파티 맴버 소환" },
// { value: "SwitchingProp", name: "데일리 퀘스트 체크" },
// { value: "TaskReservationComplete", name: "Task Reservation complete" },
// { value: "TaxiMove", name: "택시 이동" },
// { value: "TestBusinessLog", name: "테스트 비지니스 로그 전송" },
// { value: "TestUserCreate", name: "테스트 계정으로 생성" },
// { value: "TestUserInitial", name: "테스트 계정으로 초기화" },
// { value: "TestWriteNoticeChat", name: "테스트 공지사항 추가" },
// { value: "TestWriteSystemMail", name: "테스트 시스템 메일 추가" },
// { value: "UgqAbort", name: "Ugq 포기" },
// { value: "UgqApiAddSlot", name: "UgqApi 슬롯 추가" },
// { value: "UgqApiAdminLogin", name: "UgqApi 어드민 로그인" },
// { value: "UgqApiChangeState", name: "UgqApi Ugq 상태 변경" },
// { value: "UgqApiCreatorPoint", name: "UgqApi CreatorPoint 증감" },
// { value: "UgqApiLogin", name: "UgqApi 로그인" },
// { value: "UgqApiLogout", name: "UgqApi 로그아웃" },
// { value: "UgqApiQuestCraete", name: "UgqApi 퀘스트 생성" },
// { value: "UgqAssign", name: "Ugq 수락" },
// { value: "UgqDailyRewardCountRefresh", name: "Ugq 데일리 보상 리프레시" },
// { value: "UgqDeregisterBookmark", name: "Ugq 북마크 해제" },
// { value: "UgqDeregisterLike", name: "Ugq 좋아요 해제" },
// { value: "UgqReAssign", name: "Ugq 재수락" },
// { value: "UgqRegisterBookmark", name: "Ugq 북마크 등록" },
// { value: "UgqRegisterLike", name: "Ugq 좋아요 등록" },
// { value: "UgqTestAbort", name: "Test Ugq 포기" },
// { value: "UgqTestAssign", name: "Test Ugq 수락" },
// { value: "UgqTestDelete", name: "Test Ugq 삭제" },
// { value: "UpdateBeaconAppearanceCustomize", name: "비컨 외형 커스터마이징" },
// { value: "UpdateCharacterProfile", name: "캐릭터 프로필 업데이트" },
// { value: "UpdateCustomDefineUi", name: "커스텀 UI 업데이트" },
// { value: "UpdateEscape", name: "유저 탈출" },
// { value: "UpdateGameOption", name: "게임 옵션 업데이트" },
// { value: "UpdateLanguage", name: "유저 언어 업데이트" },
// { value: "UpdateUgcNpcLike", name: "NPC Like 업데이트" },
// { value: "UserBlock", name: "유저 차단" },
// { value: "UserBlockCancel", name: "유저 차단 취소" },
// { value: "UserCreate", name: "유저 생성" },
// { value: "UserLoading", name: "유저 로딩" },
// { value: "UserLogout", name: "유저 로그아웃" },
// { value: "UserLogoutSnapShot", name: "게임 로그아웃 스냅샷" },
// { value: "UserReport", name: "유저 신고" },
// { value: "Warp", name: "워프" },
// { value: "igmApiLogin", name: "igmApi 로그인" }
// ];
//
// export const logDomain = [
// { value: "BASE", name: "전체" },
// { value: "AuthLogInOut", name: "인증 로그인/인증 로그아웃" },
// { value: "GameLogInOut", name: "게임 로그인/게임 로그아웃" },
// { value: "UserCreate", name: "유저 생성" },
// { value: "User", name: "유저" },
// { value: "UserInitial", name: "유저 초기화" },
// { value: "CharacterCreate", name: "캐릭터 생성" },
// { value: "Character", name: "캐릭터" },
// { value: "Item", name: "아이템" },
// { value: "Currency", name: "재화" },
// { value: "Mail", name: "우편" },
// { value: "MailStoragePeriodExpired", name: "메일 보관 기간 만료 삭제" },
// { value: "MailProfile", name: "우편 제한 개요" },
// { value: "Stage", name: "스테이지" },
// { value: "ClaimReward", name: "클레임 리워드" },
// { value: "QuestMain", name: "퀘스트 메인" },
// { value: "QuestUgq", name: "퀘스트 Ugq" },
// { value: "QuestMail", name: "퀘스트 메일" },
// { value: "SocialAction", name: "소셜 액션" },
// { value: "MyHome", name: "마이홈" },
// { value: "Taxi", name: "택시" },
// { value: "RewardProp", name: "리워드 프랍" },
// { value: "Party", name: "파티" },
// { value: "PartyMember", name: "파티 맴버" },
// { value: "PartyVote", name: "파티 투표" },
// { value: "PartyInstance", name: "파티 인스턴스" },
// { value: "EscapePosition", name: "고립탈출" },
// { value: "UserBlock", name: "유저 차단" },
// { value: "Friend", name: "친구" },
// { value: "UserReport", name: "유저 신고" },
// { value: "TaskReservation", name: "처리못한 예약 테스크" },
// { value: "SeasonPass", name: "시즌 패스" },
// { value: "PackageLastOrderRecode", name: "패키지 마지막 획득 기록" },
// { value: "PackageRepeat", name: "패키지 연속 지급" },
// { value: "PackageState", name: "패키지 상태" },
// { value: "Craft", name: "제작" },
// { value: "CraftHelp", name: "제작 도움" },
// { value: "Cart", name: "카트" },
// { value: "Buff", name: "버프" },
// { value: "UgqApi", name: "UgqApi" },
// { value: "AIChat", name: "AI채팅" },
// { value: "Chat", name: "채팅" },
// { value: "Shop", name: "상점" },
// { value: "Calium", name: "칼리움" },
// { value: "CaliumEchoSystem", name: "칼리움 에코 시스템" },
// { value: "CaliumStorageFail", name: "칼리움 저장 실패" },
// { value: "Position", name: "위치" },
// { value: "Address", name: "주소" },
// { value: "BeaconCreate", name: "비컨 생성" },
// { value: "Beacon", name: "비컨" },
// { value: "CustomDefineUi", name: "CustomDefineUi" },
// { value: "Farming", name: "파밍" },
// { value: "FarmingReward", name: "파밍 보상" },
// { value: "RenewalShopProducts", name: "상점 리뉴얼" },
// { value: "CheatRenewalShopProducts", name: "상점 리뉴얼 치트" },
// { value: "ChangeDanceEntityState", name: "댄스 엔티티 상태 변경" },
// { value: "Land", name: "랜드" },
// { value: "Building", name: "빌딩" },
// { value: "SwitchingProp", name: "스위칭프랍" },
// { value: "LandAuction", name: "랜드 경매" },
// { value: "LandAuctionActivity", name: "랜드 경매 활성화" },
// { value: "LandAuctionBid", name: "랜드 경매 입찰" },
// { value: "LandAuctionBidPriceRefund", name: "랜드 경매 입찰금 환급" },
// { value: "BrokerApi", name: "BrokerApi" },
// { value: "Rental", name: "랜탈" },
// { value: "BuildingProfit", name: "빌딩 수익" },
// { value: "BattleObjectInteraction", name: "전투 오브젝트 인터렉션" },
// { value: "BattleObjectStateUpdate", name: "전투 오브젝트 상태 업데이트" },
// { value: "BattleReward", name: "전투 보상" },
// { value: "BattleRespawn", name: "전투 리스폰" },
// { value: "BattleRoomJoin", name: "전투 입장" },
// { value: "BattleDead", name: "전투 죽음" },
// { value: "BattleRound", name: "전투 라운드" },
// { value: "BattleSnapshot", name: "전투 스냅샷" }
// ];
export const logAction = [
{ value: "None", name: "전체" },
{ value: "AIChatDeleteCharacter", name: "AIChatDeleteCharacter" },
{ value: "AIChatDeleteUser", name: "AIChatDeleteUser" },
{ value: "AIChatGetCharacter", name: "AIChatGetCharacter" },
{ value: "AIChatIncentiveMarking", name: "AIChatIncentiveMarking" },
{ value: "AIChatIncentiveSearch", name: "AIChatIncentiveSearch" },
{ value: "AIChatJwtIssue", name: "AIChatJwtIssue" },
{ value: "AIChatJwtVerify", name: "AIChatJwtVerify" },
{ value: "AIChatPointCharge", name: "AIChatPointCharge" },
{ value: "AIChatPointChargeVerify", name: "AIChatPointChargeVerify" },
{ value: "AIChatRegisterCharacter", name: "AIChatRegisterCharacter" },
{ value: "AIChatRegisterUser", name: "AIChatRegisterUser" },
{ value: "AIChatUpdateCharacter", name: "AIChatUpdateCharacter" },
{ value: "BanParty", name: "BanParty" },
{ value: "BattleInstanceJoin", name: "BattleInstanceJoin" },
{ value: "BattleInstanceSnapshotCreate", name: "BattleInstanceSnapshotCreate" },
{ value: "BattleInstanceSnapshotSave", name: "BattleInstanceSnapshotSave" },
{ value: "BattleObjectInteraction", name: "BattleObjectInteraction" },
{ value: "BattleObjectStateUpdate", name: "BattleObjectStateUpdate" },
{ value: "BattlePodCombatOccupyReward", name: "BattlePodCombatOccupyReward" },
{ value: "BattleRoundStateUpdate", name: "BattleRoundStateUpdate" },
{ value: "BattleUserDead", name: "BattleUserDead" },
{ value: "BattleUserRespawn", name: "BattleUserRespawn" },
{ value: "BeaconAppearanceCustomize", name: "BeaconAppearanceCustomize" },
{ value: "BeaconCreate", name: "BeaconCreate" },
{ value: "BeaconEdit", name: "BeaconEdit" },
{ value: "BeaconSell", name: "BeaconSell" },
{ value: "BrokerApiAdmin", name: "BrokerApiAdmin" },
{ value: "BrokerApiPlanetAuth", name: "BrokerApiPlanetAuth" },
{ value: "BrokerApiUserExchangeOrderCompleted", name: "BrokerApiUserExchangeOrderCompleted" },
{ value: "BrokerApiUserExchangeOrderCreated", name: "BrokerApiUserExchangeOrderCreated" },
{ value: "BrokerApiUserSystemMailSend", name: "BrokerApiUserSystemMailSend" },
{ value: "BrokerApiUserEchoSystemRequest", name: "BrokerApiUserEchoSystemRequest" },
{ value: "BrokerApiUserLogin", name: "BrokerApiUserLogin" },
{ value: "BuffAdd", name: "BuffAdd" },
{ value: "BuffDelete", name: "BuffDelete" },
{ value: "CaliumSyncEchoSystem", name: "CaliumSyncEchoSystem" },
{ value: "CancelFriendRequest", name: "CancelFriendRequest" },
{ value: "CartAdd", name: "CartAdd" },
{ value: "CartDelete", name: "CartDelete" },
{ value: "CartPurchase", name: "CartPurchase" },
{ value: "ChangeConvertCaliumInfo", name: "ChangeConvertCaliumInfo" },
{ value: "CharacterAppearanceCustomize", name: "CharacterAppearanceCustomize" },
{ value: "CharacterAppearanceUpdate", name: "CharacterAppearanceUpdate" },
{ value: "CharacterCreate", name: "CharacterCreate" },
{ value: "CharacterLoading", name: "CharacterLoading" },
{ value: "ChatChannel", name: "ChatChannel" },
{ value: "ChatNormal", name: "ChatNormal" },
{ value: "ChatNotice", name: "ChatNotice" },
{ value: "ChatParty", name: "ChatParty" },
{ value: "ChatWhisper", name: "ChatWhisper" },
{ value: "CheatAllCraftFinish", name: "CheatAllCraftFinish" },
{ value: "CheatCommandChangeNickName", name: "CheatCommandChangeNickName" },
{ value: "CheatCommandCharacterInit", name: "CheatCommandCharacterInit" },
{ value: "CheatCommandClaimReset", name: "CheatCommandClaimReset" },
{ value: "CheatCommandClaimUpdate", name: "CheatCommandClaimUpdate" },
{ value: "CheatCommandCraftHelpInit", name: "CheatCommandCraftHelpInit" },
{ value: "CheatCommandDeleteQuest", name: "CheatCommandDeleteQuest" },
{ value: "CheatCommandGainLand", name: "CheatCommandGainLand" },
{ value: "CheatCommandIncreaseExp", name: "CheatCommandIncreaseExp" },
{ value: "CheatCommandItem", name: "CheatCommandItem" },
{ value: "CheatCommandItemLevelUp", name: "CheatCommandItemLevelUp" },
{ value: "CheatCommandLandAuctionBlindSet", name: "CheatCommandLandAuctionBlindSet" },
{ value: "CheatCommandLandAuctionCanel", name: "CheatCommandLandAuctionCanel" },
{ value: "CheatCommandLandAuctionReserve", name: "CheatCommandLandAuctionReserve" },
{ value: "CheatCommandLandAuctionStart", name: "CheatCommandLandAuctionStart" },
{ value: "CheatCommandPackageSend", name: "CheatCommandPackageSend" },
{ value: "CheatCommandQuestAccept", name: "CheatCommandQuestAccept" },
{ value: "CheatCommandQuestComplete", name: "CheatCommandQuestComplete" },
{ value: "CheatCommandRegisterCraftRecipe", name: "CheatCommandRegisterCraftRecipe" },
{ value: "CheatCommandResetAllQuest", name: "CheatCommandResetAllQuest" },
{ value: "CheatCommandResetEscapePosition", name: "CheatCommandResetEscapePosition" },
{ value: "CheatCommandResetMailCount", name: "CheatCommandResetMailCount" },
{ value: "CheatCommandSeasonPassInit", name: "CheatCommandSeasonPassInit" },
{ value: "CheatCommandSendMail", name: "CheatCommandSendMail" },
{ value: "CheatCommandShopProductInit", name: "CheatCommandShopProductInit" },
{ value: "CheatCommandShopProductRenewal", name: "CheatCommandShopProductRenewal" },
{ value: "ClaimReward", name: "ClaimReward" },
{ value: "ConvertCalium", name: "ConvertCalium" },
{ value: "ConvertExchangeCalium", name: "ConvertExchangeCalium" },
{ value: "CraftFinish", name: "CraftFinish" },
{ value: "CraftHelp", name: "CraftHelp" },
{ value: "CraftRecipeRegister", name: "CraftRecipeRegister" },
{ value: "CraftStart", name: "CraftStart" },
{ value: "CraftStop", name: "CraftStop" },
{ value: "CreateCaliumContent", name: "CreateCaliumContent" },
{ value: "CreateParty", name: "CreateParty" },
{ value: "CreatePartyInstance", name: "CreatePartyInstance" },
{ value: "DailyQuestCheck", name: "DailyQuestCheck" },
{ value: "DanceEntityStateEnd", name: "DanceEntityStateEnd" },
{ value: "DanceEntityStateStart", name: "DanceEntityStateStart" },
{ value: "DeleteMyhome", name: "DeleteMyhome" },
{ value: "DestroyParty", name: "DestroyParty" },
{ value: "EndPartyVote", name: "EndPartyVote" },
{ value: "EnterMyhome", name: "EnterMyhome" },
{ value: "EnterMyhomeEditRoom", name: "EnterMyhomeEditRoom" },
{ value: "FailCaliumEchoSystem", name: "FailCaliumEchoSystem" },
{ value: "FailCaliumStorageRollBack", name: "FailCaliumStorageRollBack" },
{ value: "FarmingCancel", name: "FarmingCancel" },
{ value: "FarmingComplete", name: "FarmingComplete" },
{ value: "FarmingIncompletedReward", name: "FarmingIncompletedReward" },
{ value: "FarmingStart", name: "FarmingStart" },
{ value: "FillupCalium", name: "FillupCalium" },
{ value: "FriendAdd", name: "FriendAdd" },
{ value: "FriendDelete", name: "FriendDelete" },
{ value: "GainLandProfit", name: "GainLandProfit" },
{ value: "InviteParty", name: "InviteParty" },
{ value: "ItemBuy", name: "ItemBuy" },
{ value: "ItemDestroy", name: "ItemDestroy" },
{ value: "ItemRandomBoxUse", name: "ItemRandomBoxUse" },
{ value: "ItemTattooChangeAttribute", name: "ItemTattooChangeAttribute" },
{ value: "ItemTattooLevelUp", name: "ItemTattooLevelUp" },
{ value: "ItemUse", name: "ItemUse" },
{ value: "JoinInstance", name: "JoinInstance" },
{ value: "JoinParty", name: "JoinParty" },
{ value: "JoinPartyInstance", name: "JoinPartyInstance" },
{ value: "KickFriendsFromMyhome", name: "KickFriendsFromMyhome" },
{ value: "LandAuctionActivity", name: "LandAuctionActivity" },
{ value: "LandAuctionBid", name: "LandAuctionBid" },
{ value: "LandAuctionBidPriceRefund", name: "LandAuctionBidPriceRefund" },
{ value: "LandAuctionCheck", name: "LandAuctionCheck" },
{ value: "LeaveInstance", name: "LeaveInstance" },
{ value: "LeaveParty", name: "LeaveParty" },
{ value: "LeavePartyInstance", name: "LeavePartyInstance" },
{ value: "LoginToGame", name: "LoginToGame" },
{ value: "LoginToGameSnapShot", name: "LoginToGameSnapShot" },
{ value: "LoginToUserAuth", name: "LoginToUserAuth" },
{ value: "MailAiChatIncentivePoint", name: "MailAiChatIncentivePoint" },
{ value: "MailDestroy", name: "MailDestroy" },
{ value: "MailGetSystemMail", name: "MailGetSystemMail" },
{ value: "MailInitSendCount", name: "MailInitSendCount" },
{ value: "MailRead", name: "MailRead" },
{ value: "MailSend", name: "MailSend" },
{ value: "MailTaken", name: "MailTaken" },
{ value: "ModifyLandInfo", name: "ModifyLandInfo" },
{ value: "MoneyChange", name: "MoneyChange" },
{ value: "ProductGive", name: "ProductGive" },
{ value: "ProductOpenFailed", name: "ProductOpenFailed" },
{ value: "ProductOpenSuccess", name: "ProductOpenSuccess" },
{ value: "QuestMailSend", name: "QuestMailSend" },
{ value: "QuestMainAbort", name: "QuestMainAbort" },
{ value: "QuestMainAssignByDialogue", name: "QuestMainAssignByDialogue" },
{ value: "QuestMainAssignForce", name: "QuestMainAssignForce" },
{ value: "QuestMainRefuse", name: "QuestMainRefuse" },
{ value: "QuestMainRepeatTimeInit", name: "QuestMainRepeatTimeInit" },
{ value: "QuestMainRepeatTimeRefresh", name: "QuestMainRepeatTimeRefresh" },
{ value: "QuestMainReward", name: "QuestMainReward" },
{ value: "QuestMainTask", name: "QuestMainTask" },
{ value: "QuestTaskUpdate", name: "QuestTaskUpdate" },
{ value: "RefuseFriendRequest", name: "RefuseFriendRequest" },
{ value: "RenameFriendFolder", name: "RenameFriendFolder" },
{ value: "RenameMyhome", name: "RenameMyhome" },
{ value: "RenewalShopProducts", name: "RenewalShopProducts" },
{ value: "RentFloor", name: "RentFloor" },
{ value: "ReplyInviteParty", name: "ReplyInviteParty" },
{ value: "ReplySummonParty", name: "ReplySummonParty" },
{ value: "ReservationEnterToServer", name: "ReservationEnterToServer" },
{ value: "RewardProp", name: "RewardProp" },
{ value: "SaveMyhome", name: "SaveMyhome" },
{ value: "SeasonPassBuyCharged", name: "SeasonPassBuyCharged" },
{ value: "SeasonPassStartNew", name: "SeasonPassStartNew" },
{ value: "SeasonPassTakeReward", name: "SeasonPassTakeReward" },
{ value: "SendFriendRequest", name: "SendFriendRequest" },
{ value: "ShopChangeProductTradingMeter", name: "ShopChangeProductTradingMeter" },
{ value: "ShopGetProductTradingMeter", name: "ShopGetProductTradingMeter" },
{ value: "ShopGetRePurchase", name: "ShopGetRePurchase" },
{ value: "ShopPurchase", name: "ShopPurchase" },
{ value: "ShopRePurchase", name: "ShopRePurchase" },
{ value: "ShopSell", name: "ShopSell" },
{ value: "StageConcertStart", name: "StageConcertStart" },
{ value: "StageEnter", name: "StageEnter" },
{ value: "StageExit", name: "StageExit" },
{ value: "StartPartyVote", name: "StartPartyVote" },
{ value: "SummonParty", name: "SummonParty" },
{ value: "SwitchingProp", name: "SwitchingProp" },
{ value: "TaskReservationComplete", name: "TaskReservationComplete" },
{ value: "TaxiMove", name: "TaxiMove" },
{ value: "TestBusinessLog", name: "TestBusinessLog" },
{ value: "TestUserCreate", name: "TestUserCreate" },
{ value: "TestUserInitial", name: "TestUserInitial" },
{ value: "TestWriteNoticeChat", name: "TestWriteNoticeChat" },
{ value: "TestWriteSystemMail", name: "TestWriteSystemMail" },
{ value: "UgqAbort", name: "UgqAbort" },
{ value: "UgqApiAddSlot", name: "UgqApiAddSlot" },
{ value: "UgqApiAdminLogin", name: "UgqApiAdminLogin" },
{ value: "UgqApiChangeState", name: "UgqApiChangeState" },
{ value: "UgqApiCreatorPoint", name: "UgqApiCreatorPoint" },
{ value: "UgqApiLogin", name: "UgqApiLogin" },
{ value: "UgqApiLogout", name: "UgqApiLogout" },
{ value: "UgqApiQuestCraete", name: "UgqApiQuestCraete" },
{ value: "UgqAssign", name: "UgqAssign" },
{ value: "UgqDailyRewardCountRefresh", name: "UgqDailyRewardCountRefresh" },
{ value: "UgqDeregisterBookmark", name: "UgqDeregisterBookmark" },
{ value: "UgqDeregisterLike", name: "UgqDeregisterLike" },
{ value: "UgqReAssign", name: "UgqReAssign" },
{ value: "UgqRegisterBookmark", name: "UgqRegisterBookmark" },
{ value: "UgqRegisterLike", name: "UgqRegisterLike" },
{ value: "UgqTestAbort", name: "UgqTestAbort" },
{ value: "UgqTestAssign", name: "UgqTestAssign" },
{ value: "UgqTestDelete", name: "UgqTestDelete" },
{ value: "UpdateBeaconAppearanceCustomize", name: "UpdateBeaconAppearanceCustomize" },
{ value: "UpdateCharacterProfile", name: "UpdateCharacterProfile" },
{ value: "UpdateCustomDefineUi", name: "UpdateCustomDefineUi" },
{ value: "UpdateEscape", name: "UpdateEscape" },
{ value: "UpdateGameOption", name: "UpdateGameOption" },
{ value: "UpdateLanguage", name: "UpdateLanguage" },
{ value: "UpdateUgcNpcLike", name: "UpdateUgcNpcLike" },
{ value: "UserBlock", name: "UserBlock" },
{ value: "UserBlockCancel", name: "UserBlockCancel" },
{ value: "UserCreate", name: "UserCreate" },
{ value: "UserLoading", name: "UserLoading" },
{ value: "UserLogout", name: "UserLogout" },
{ value: "UserLogoutSnapShot", name: "UserLogoutSnapShot" },
{ value: "UserReport", name: "UserReport" },
{ value: "Warp", name: "Warp" },
{ value: "igmApiLogin", name: "igmApiLogin" }
];
export const logDomain = [
{ value: "BASE", name: "전체" },
{ value: "AuthLogInOut", name: "AuthLogInOut" },
{ value: "GameLogInOut", name: "GameLogInOut" },
{ value: "UserCreate", name: "UserCreate" },
{ value: "User", name: "User" },
{ value: "UserInitial", name: "UserInitial" },
{ value: "CharacterCreate", name: "CharacterCreate" },
{ value: "Character", name: "Character" },
{ value: "Item", name: "Item" },
{ value: "Currency", name: "Currency" },
{ value: "Mail", name: "Mail" },
{ value: "MailStoragePeriodExpired", name: "MailStoragePeriodExpired" },
{ value: "MailProfile", name: "MailProfile" },
{ value: "Stage", name: "Stage" },
{ value: "ClaimReward", name: "ClaimReward" },
{ value: "QuestMain", name: "QuestMain" },
{ value: "QuestUgq", name: "QuestUgq" },
{ value: "QuestMail", name: "QuestMail" },
{ value: "SocialAction", name: "SocialAction" },
{ value: "MyHome", name: "MyHome" },
{ value: "Taxi", name: "Taxi" },
{ value: "RewardProp", name: "RewardProp" },
{ value: "Party", name: "Party" },
{ value: "PartyMember", name: "PartyMember" },
{ value: "PartyVote", name: "PartyVote" },
{ value: "PartyInstance", name: "PartyInstance" },
{ value: "EscapePosition", name: "EscapePosition" },
{ value: "UserBlock", name: "UserBlock" },
{ value: "Friend", name: "Friend" },
{ value: "UserReport", name: "UserReport" },
{ value: "TaskReservation", name: "TaskReservation" },
{ value: "SeasonPass", name: "SeasonPass" },
{ value: "PackageLastOrderRecode", name: "PackageLastOrderRecode" },
{ value: "PackageRepeat", name: "PackageRepeat" },
{ value: "PackageState", name: "PackageState" },
{ value: "Craft", name: "Craft" },
{ value: "CraftHelp", name: "CraftHelp" },
{ value: "Cart", name: "Cart" },
{ value: "Buff", name: "Buff" },
{ value: "UgqApi", name: "UgqApi" },
{ value: "AIChat", name: "AIChat" },
{ value: "Chat", name: "Chat" },
{ value: "Shop", name: "Shop" },
{ value: "Calium", name: "Calium" },
{ value: "CaliumEchoSystem", name: "CaliumEchoSystem" },
{ value: "CaliumStorageFail", name: "CaliumStorageFail" },
{ value: "Position", name: "Position" },
{ value: "Address", name: "Address" },
{ value: "BeaconCreate", name: "BeaconCreate" },
{ value: "Beacon", name: "Beacon" },
{ value: "CustomDefineUi", name: "CustomDefineUi" },
{ value: "Farming", name: "Farming" },
{ value: "FarmingReward", name: "FarmingReward" },
{ value: "RenewalShopProducts", name: "RenewalShopProducts" },
{ value: "CheatRenewalShopProducts", name: "CheatRenewalShopProducts" },
{ value: "ChangeDanceEntityState", name: "ChangeDanceEntityState" },
{ value: "Land", name: "Land" },
{ value: "Building", name: "Building" },
{ value: "SwitchingProp", name: "SwitchingProp" },
{ value: "LandAuction", name: "LandAuction" },
{ value: "LandAuctionActivity", name: "LandAuctionActivity" },
{ value: "LandAuctionBid", name: "LandAuctionBid" },
{ value: "LandAuctionBidPriceRefund", name: "LandAuctionBidPriceRefund" },
{ value: "BrokerApi", name: "BrokerApi" },
{ value: "Rental", name: "Rental" },
{ value: "BuildingProfit", name: "BuildingProfit" },
{ value: "BattleObjectInteraction", name: "BattleObjectInteraction" },
{ value: "BattleObjectStateUpdate", name: "BattleObjectStateUpdate" },
{ value: "BattleReward", name: "BattleReward" },
{ value: "BattleRespawn", name: "BattleRespawn" },
{ value: "BattleRoomJoin", name: "BattleRoomJoin" },
{ value: "BattleDead", name: "BattleDead" },
{ value: "BattleRound", name: "BattleRound" },
{ value: "BattleSnapshot", name: "BattleSnapshot" }
];

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,9 +47,23 @@ export const authType = {
landDelete: 45, landDelete: 45,
battleEventRead: 46, battleEventRead: 46,
battleEventUpdate: 47, battleEventUpdate: 47,
battleEventDelete: 48 battleEventDelete: 48,
businessLogRead: 49,
levelReader: 999,
levelMaster: 9999,
levelDeveloper: 99999,
}; };
export const adminAuthLevel = {
NONE: "None",
READER: "Reader",
MASTER: "Master",
DEVELOPER: "Developer",
}
export const TabList = [ export const TabList = [
{ title: '기본정보' }, { title: '기본정보' },
{ title: '아바타' }, { title: '아바타' },

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,94 @@ 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';
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 [pwPop, setPwPop] = useState('hidden'); const {
const [gmModal, setGmModal] = useState('hidden'); modalState,
handleModalView,
handleModalClose
} = useModal({
userKick: 'hidden',
gmLevelChange: 'hidden',
pwChange: 'hidden'
});
const [alertMsg, setAlertMsg] = useState('');
const [dataList, setDataList] = useState({}); const [dataList, setDataList] = useState({});
const [adminLevel, setAdminLevel] = useState('0'); const [adminLevel, setAdminLevel] = useState('0');
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [authDelete, setAuthDelete] = useState(false);
const handleClick = () => { useEffect(() => {
if (pwPop === 'hidden') setPwPop('view'); setAuthDelete(authInfo?.auth_list?.some(auth => auth.id === authType.userSearchDelete));
else setPwPop('hidden'); }, [authInfo]);
};
useEffect(() => { useEffect(() => {
fetchData(); 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":
setAdminLevel(param);
handleCancel(); handleModalView('gmLevelChange');
await fetchData(); break;
} case "userKickSubmit":
handleModalView('userKick');
break;
case "gmLevelChange":
setLoading(true);
const handleCancel = () => { params.guid = userInfo.guid;
setGmModal('hidden'); params.admin_level = adminLevel;
await UserChangeAdminLevel(token, params).then(data =>{
setAlertMsg(t('USER_GM_CHANGE_COMPLETE'))
}).catch(error => {
console.log(error);
}).finally(() => {
setLoading(false);
handleModalClose('gmLevelChange');
fetchData();
});
break;
case "userKick":
params.guid = userInfo.guid;
await UserKick(token, params).then((data) =>{
setAlertMsg(t('USER_KICK_COMPLETE'))
}).catch(error => {
console.log(error);
}).finally(() => {
setLoading(false);
handleModalClose('userKick');
fetchData();
});
break;
case "registComplete":
handleModalClose('registComplete');
break;
case "warning":
setAlertMsg('');
break;
}
} }
return ( return (
@@ -76,11 +117,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 +129,16 @@ 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 theme={(dataList.user_session && authDelete) ? "line" : "disable"}
<tr> id={"user_session"}
<th>친구 추천코드</th> name="kick"
<td>{dataList.user_info && dataList.user_info.friend_code}</td> text="KICK"
handleClick={() => handleSubmit('userKickSubmit')}
disabled={!dataList.user_session && !authDelete}
/>}
</StatusCell>
</tr> </tr>
<tr> <tr>
<th>계정 생성일</th> <th>계정 생성일</th>
@@ -103,10 +148,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 +163,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 && 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}
@@ -149,7 +193,7 @@ const UserDefaultInfo = ({ userInfo }) => {
hidden={true} hidden={true}
onClick={e => { onClick={e => {
e.preventDefault(); e.preventDefault();
handleClick(); handleModalClose('pwChange');
}}></EditButton> }}></EditButton>
</td> </td>
</tr> </tr>
@@ -172,15 +216,36 @@ const UserDefaultInfo = ({ userInfo }) => {
</tbody> </tbody>
</UserInfoTable> </UserInfoTable>
</div> </div>
<NicknameChangeModal pwPop={pwPop} handleClick={handleClick} dataList={dataList} /> <NicknameChangeModal pwPop={modalState.pwChangeModal} handleClick={() => handleModalClose('pwChange')} dataList={dataList} />
<DynamicModal <DynamicModal
modalType={modalTypes.childOkCancel} modalType={modalTypes.confirmOkCancel}
view={gmModal} view={modalState.gmLevelChangeModal}
modalText={t('USER_GM_CHANGE')} modalText={t('USER_GM_CHANGE')}
handleCancel={handleCancel} handleSubmit={() => handleSubmit('gmLevelChange')}
handleSubmit={handleSubmit} handleCancel={() => handleModalClose('gmLevelChange')}
/>
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.userKickModal}
modalText={t('USER_KICK_CONFIRM')}
handleSubmit={() => handleSubmit('userKick')}
handleCancel={() => handleModalClose('userKick')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => setAlertMsg('')}
/> />
</> </>
); );
}; };
export default UserDefaultInfo; export default UserDefaultInfo;
const StatusCell = styled.td`
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
`;

View File

@@ -12,53 +12,67 @@ 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 DynamicModal from '../common/modal/DynamicModal';
import { authType, ivenTabType } from '../../assets/data'; import { authType, ivenTabType, opMailType } 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 { eventSearchType, opPickupType, opReadType, opYNType } from '../../assets/data/options';
import { useDynamoDBPagination, useModal } from '../../hooks/hook';
import { DynamoPagination } from '../common';
const UserMailInfo = ({ userInfo }) => { const UserMailInfo = ({ userInfo }) => {
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const { t } = useTranslation(); const { t } = useTranslation();
const authInfo = useRecoilValue(authList); const authInfo = useRecoilValue(authList);
//데이터 리스트
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 [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',
deleteSubmit: 'hidden',
deleteComplete: 'hidden',
deleteItemComplete: '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 });
}; };
@@ -79,20 +93,6 @@ const UserMailInfo = ({ userInfo }) => {
} }
}; };
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; let result;
@@ -145,7 +145,7 @@ const UserMailInfo = ({ userInfo }) => {
// if(idx >= 0) { // if(idx >= 0) {
// dataList.mail_list.splice(idx, 1); // dataList.mail_list.splice(idx, 1);
// } // }
fetchData(option); fetchPage(pagination.currentPage);
break; break;
case "deleteItemComplete": case "deleteItemComplete":
handleModalClose('deleteItemComplete'); handleModalClose('deleteItemComplete');
@@ -165,21 +165,28 @@ const UserMailInfo = ({ userInfo }) => {
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>
@@ -195,7 +202,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}>
@@ -211,10 +218,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> {/* <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())} {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> */} </td> */}
@@ -228,9 +235,6 @@ const UserMailInfo = ({ userInfo }) => {
)} )}
{option === 'SEND' && ( {option === 'SEND' && (
<> <>
{/* <CheckWrapper>
<CheckBox id="viewDelSendMail" label="삭제 우편 보기" />
</CheckWrapper> */}
<UserTableWrapper> <UserTableWrapper>
<UserDefaultTable> <UserDefaultTable>
<thead> <thead>
@@ -242,7 +246,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 +271,8 @@ const UserMailInfo = ({ userInfo }) => {
</UserTableWrapper> </UserTableWrapper>
</> </>
)} )}
{/*상세*/} {/*상세*/}
<MailDetailModal <MailDetailModal
mailModal={modalState.detailModal} mailModal={modalState.detailModal}
@@ -365,22 +371,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 +387,41 @@ 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;
}
`;
const PaginationButtons = styled.div`
display: flex;
align-items: center;
gap: 10px;
`;
const PaginationButton = styled.button`
background-color: ${props => props.disabled ? '#e0e0e0' : '#6c7eb7'};
color: ${props => props.disabled ? '#a0a0a0' : 'white'};
border: none;
padding: 6px 12px;
border-radius: 4px;
font-size: 12px;
cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
transition: background-color 0.2s;
&:hover {
background-color: ${props => props.disabled ? '#e0e0e0' : '#5a6a9b'};
}
`;
const PageInfo = styled.div`
font-size: 12px;
font-weight: 500;
color: #666;
`;

View File

@@ -1,25 +1,43 @@
import BoardInfoModal from './modal/BoardInfoModal'; import BoardInfoModal from './modal/BoardInfoModal';
import BoardRegistModal from './modal/BoardRegistModal'; import BoardRegistModal from './modal/BoardRegistModal';
import MailDetailModal from './modal/MailDetailModal'; import MailDetailModal from './modal/MailDetailModal';
import MailListSearchBar from './searchBar/MailListSearchBar'; import LandAuctionModal from './modal/LandAuctionModal'
import BattleEventModal from './modal/BattleEventModal'
import ReportListAnswerModal from './modal/ReportListAnswerModal'; import ReportListAnswerModal from './modal/ReportListAnswerModal';
import ReportListDetailModal from './modal/ReportListDetailModal'; import ReportListDetailModal from './modal/ReportListDetailModal';
import ReportListSearchBar from './searchBar/ReportListSearchBar';
import UserBlockDetailModal from './modal/UserBlockDetailModal'; import UserBlockDetailModal from './modal/UserBlockDetailModal';
import OwnerChangeModal from './modal/OwnerChangeModal';
//searchbar
import SearchFilter from './searchBar/SearchFilter';
import ReportListSearchBar from './searchBar/ReportListSearchBar';
import UserBlockSearchBar from './searchBar/UserBlockSearchBar'; import UserBlockSearchBar from './searchBar/UserBlockSearchBar';
import WhiteListSearchBar from './WhiteListRegistBar';
import ReportListSummary from './ReportListSummary';
import ItemsSearchBar from './searchBar/ItemsSearchBar'; import ItemsSearchBar from './searchBar/ItemsSearchBar';
import EventListSearchBar from './searchBar/EventListSearchBar'; import EventListSearchBar from './searchBar/EventListSearchBar';
import LandAuctionSearchBar from './searchBar/LandAuctionSearchBar' import LandAuctionSearchBar from './searchBar/LandAuctionSearchBar'
import LandAuctionModal from './modal/LandAuctionModal' import MailListSearchBar from './searchBar/MailListSearchBar';
import BattleEventModal from './modal/BattleEventModal' import LandInfoSearchBar from './searchBar/LandInfoSearchBar';
import BusinessLogSearchBar from './searchBar/BusinessLogSearchBar';
import DataInitSearchBar from './searchBar/DataInitSearchBar';
import LogViewSearchBar from './searchBar/LogViewSearchBar';
import AdminViewSearchBar from './searchBar/AdminViewSearchBar';
import CaliumRequestSearchBar from './searchBar/CaliumRequestSearchBar';
//etc
import ReportListSummary from './ReportListSummary';
import WhiteListSearchBar from './WhiteListRegistBar';
export { export {
BoardInfoModal, BoardInfoModal,
BoardRegistModal, BoardRegistModal,
MailDetailModal, MailDetailModal,
SearchFilter,
MailListSearchBar, MailListSearchBar,
LandInfoSearchBar,
BusinessLogSearchBar,
DataInitSearchBar,
LogViewSearchBar,
AdminViewSearchBar,
CaliumRequestSearchBar,
ReportListAnswerModal, ReportListAnswerModal,
ReportListDetailModal, ReportListDetailModal,
ReportListSearchBar, ReportListSearchBar,
@@ -31,5 +49,6 @@ export {
EventListSearchBar, EventListSearchBar,
LandAuctionSearchBar, LandAuctionSearchBar,
LandAuctionModal, LandAuctionModal,
BattleEventModal BattleEventModal,
OwnerChangeModal
}; };

View File

@@ -22,7 +22,7 @@ import {
import { modalTypes } from '../../../assets/data'; import { modalTypes } from '../../../assets/data';
import { DynamicModal, Modal, SingleDatePicker, SingleTimePicker } from '../../common'; import { DynamicModal, Modal, SingleDatePicker, SingleTimePicker } from '../../common';
import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../../assets/data/adminConstants'; import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../../assets/data/adminConstants';
import { useModal } from '../../../utils/hook'; import { useModal } from '../../../hooks/hook';
import { convertKTCDate } from '../../../utils'; import { convertKTCDate } from '../../../utils';
import { import {
battleEventHotTime, battleEventHotTime,

View File

@@ -31,7 +31,7 @@ import {
TYPE_REGISTRY, TYPE_REGISTRY,
} from '../../../assets/data/adminConstants'; } from '../../../assets/data/adminConstants';
import { landAuctionStatus, landAuctionStatusType, languageType, CurrencyType } from '../../../assets/data'; import { landAuctionStatus, landAuctionStatusType, languageType, CurrencyType } from '../../../assets/data';
import { useModal } from '../../../utils/hook'; import { useModal } from '../../../hooks/hook';
import { convertKTCDate } from '../../../utils'; import { convertKTCDate } from '../../../utils';
import { msToMinutes } from '../../../utils/date'; import { msToMinutes } from '../../../utils/date';

View File

@@ -0,0 +1,395 @@
import React, { useState, Fragment, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import Button from '../../common/button/Button';
import Loading from '../../common/Loading';
import {
Title,
BtnWrapper,
SearchBarAlert, SelectInput,
} from '../../../styles/Components';
import {
FormInput,
FormLabel,
MessageWrapper,
FormRowGroup,
FormStatusBar,
FormStatusLabel,
FormStatusWarning,
FormButtonContainer, FormGroup, FormItemGroup, SubText,
} from '../../../styles/ModuleComponents';
import { modalTypes } from '../../../assets/data';
import { DynamicModal, Modal, SingleDatePicker, SingleTimePicker } from '../../common';
import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../../assets/data/adminConstants';
import { useModal } from '../../../hooks/hook';
import { convertKTCDate } from '../../../utils';
import { BattleEventModify, BattleEventSingleRegist } from '../../../apis/Battle';
import { battleEventStatusType } from '../../../assets/data/types';
import { isValidDayRange } from '../../../utils/date';
import CheckBox from '../../common/input/CheckBox';
import { LandOwnedChangesRegist, LandOwnerChangesDelete, UserInfoView } from '../../../apis';
const OwnerChangeModal = ({ modalType, detailView, handleDetailView, content, setDetailData }) => {
const { t } = useTranslation();
const token = sessionStorage.getItem('token');
const [loading, setLoading] = useState(false); // 로딩 창
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
cancel: 'hidden',
registConfirm: 'hidden',
registComplete: 'hidden'
});
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
const [alertMsg, setAlertMsg] = useState('');
const [resultData, setResultData] = useState(initData); //데이터 정보
useEffect(() => {
if(content && Object.keys(content).length > 0){
const ownerChanges = content.owner_changes;
let changes_info;
if(ownerChanges && ownerChanges.length > 0){
changes_info = ownerChanges.filter(item => item.status === 'WAIT').reduce((maxItem, current) => {
return (!maxItem || current.id > maxItem.id ) ? current : maxItem;
}, null);
}
setResultData({
...resultData,
land_id: content.land_id,
land_name: content.land_name,
building_id: content.building_id,
building_name: content.building_name,
is_reserve: changes_info?.is_reserve || false,
reservation_dt: (changes_info && convertKTCDate(changes_info.reservation_dt)) || new Date(),
user_guid: changes_info?.user_guid || '',
user_name: changes_info?.user_name || '',
id: changes_info?.id || null
});
}
}, [modalType, content]);
useEffect(() => {
if (checkCondition()) {
setIsNullValue(false);
} else {
setIsNullValue(true);
}
}, [resultData]);
// 날짜 변경 핸들러
const handleStartDateChange = (date) => {
if (!date) return;
const newDate = new Date(date);
setResultData(prev => ({
...prev,
reservation_dt: newDate
}));
};
// 시간 변경 핸들러
const handleStartTimeChange = (time) => {
if (!time) return;
const newDateTime = resultData.reservation_dt
? new Date(resultData.reservation_dt)
: new Date();
newDateTime.setHours(
time.getHours(),
time.getMinutes(),
0,
0
);
setResultData(prev => ({
...prev,
reservation_dt: newDateTime
}));
};
const handleReset = () => {
setDetailData({});
setResultData(initData);
handleDetailView();
}
const handleSubmit = async (type, param = null) => {
switch (type) {
case "submit":
if (!checkCondition()) return;
handleModalView('registConfirm');
break;
case "cancel":
handleModalView('cancel');
break;
case "cancelConfirm":
handleModalClose('cancel');
handleReset();
break;
case "user":
if(isView()) return;
const guid = resultData.user_guid;
if(!guid || guid.length !== 32){
setAlertMsg(t('WARNING_GUID_CHECK'))
return;
}
setLoading(true);
await UserInfoView(token, guid).then(data => {
if(Object.keys(data).length === 0){
setAlertMsg(t('WARNING_GUID_CHECK'));
setResultData({ ...resultData, user_name: '' })
return;
}
const nickname = data.char_info.character_name;
setResultData({ ...resultData, user_name: nickname })
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
}).finally(()=>{
setLoading(false);
});
break;
case "registConfirm":
setLoading(true);
if(isView()){
setLoading(false);
handleModalClose('registConfirm');
const resvDt = resultData.reservation_dt;
const now = new Date();
if(resvDt < now){
setAlertMsg(t('LAND_OWNED_CHANGES_DELETE_TIME_WARNING'));
handleReset();
return;
}
await LandOwnerChangesDelete(token, resultData).then(data => {
handleModalClose('registConfirm');
if(data.result === "SUCCESS") {
handleModalView('registComplete');
}else if(data.result === "ERROR_LAND_OWNER_CHANGES_RESERVATION"){
setAlertMsg(t('LAND_OWNED_CHANGES_DELETE_STATUS_WARNING'));
}else{
setAlertMsg(t('DELETE_FAIL'));
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
}).finally(() => {
setLoading(false);
});
}else{
await LandOwnedChangesRegist(token, resultData).then(data => {
handleModalClose('registConfirm');
if(data.result === "SUCCESS") {
handleModalView('registComplete');
}else if(data.result === "GUID_CHECK"){
setAlertMsg(t('WARNING_GUID_CHECK'));
}else if(data.result === "ERROR_LAND_OWNER_DUPLICATION"){
setAlertMsg(t('LAND_OWNER_DUPLICATION_WARNING'));
}else if(data.result === "ERROR_LAND_OWNER_CHANGES_DUPLICATION"){
setAlertMsg(t('LAND_OWNED_CHANGES_REGIST_DUPLICATION_WARNING'));
}else{
setAlertMsg(t('REGIST_FAIL'));
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
}).finally(() => {
setLoading(false);
});
}
break;
case "registComplete":
handleModalClose('registComplete');
handleReset();
break;
case "warning":
setAlertMsg('');
break;
}
}
const checkCondition = () => {
return (
resultData.land_id !== ''
&& resultData.land_name !== ''
&& resultData.user_guid !== ''
&& resultData.user_name !== ''
&& (!resultData.is_reserve || (resultData.is_reserve && resultData.reservation_dt !== ''))
);
};
const isView = (label) => {
switch (label) {
case "modify":
return modalType === TYPE_MODIFY;
case "registry":
return modalType === TYPE_REGISTRY;
case "reservation_dt":
case "user":
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY &&resultData?.id === null);
default:
return modalType === TYPE_MODIFY && resultData?.id !== null;
}
}
return (
<>
<Modal min="760px" $view={detailView}>
<Title $align="center">소유권 변경</Title>
<MessageWrapper>
<FormRowGroup>
<FormItemGroup>
<CheckBox
label="예약"
id="reserve"
checked={resultData.is_reserve}
setData={e => setResultData({ ...resultData, is_reserve: e.target.checked, reservation_dt: new Date() })}
disabled={!isView('user')}
/>
</FormItemGroup>
{resultData.is_reserve && (
<>
<SingleDatePicker
label="발송시간"
disabled={!isView('reservation_dt')}
dateLabel="발송 일자"
onDateChange={handleStartDateChange}
selectedDate={resultData?.reservation_dt}
/>
<SingleTimePicker
disabled={!isView('reservation_dt')}
selectedTime={resultData?.reservation_dt}
onTimeChange={handleStartTimeChange}
/>
</>
)}
</FormRowGroup>
<FormRowGroup><SubText>* 예약 발송 미선택 등록과 함께 우편이 즉시 발송됩니다.</SubText></FormRowGroup>
<FormRowGroup>
<FormLabel>변경 랜드</FormLabel>
<FormInput
type="text"
width='150px'
value={resultData?.land_id}
readOnly={true}
handleClick={() => handleSubmit('user')}
disabled={!isView('user')}
/>
<FormInput
type="text"
width='250px'
value={resultData?.land_name}
readOnly={true}
handleClick={() => handleSubmit('user')}
disabled={!isView('user')}
/>
</FormRowGroup>
<FormRowGroup>
<FormLabel>수신 대상</FormLabel>
<FormInput
type="text"
placeholder="guid 입력"
width='300px'
value={resultData?.user_guid}
onChange={e => setResultData({ ...resultData, user_guid: e.target.value })}
disabled={!isView('user')}
/>
<Button
text="확인"
theme={!isView() ? 'primary' : 'disable'}
type="submit"
size="large"
width="100px"
handleClick={() => handleSubmit('user')}
/>
</FormRowGroup>
<FormRowGroup>
<FormLabel>캐릭터명</FormLabel>
<FormInput
type="text"
width='300px'
value={resultData?.user_name}
readOnly={true}
disabled={!isView('user')}
/>
</FormRowGroup>
{!isView() && isNullValue && <SearchBarAlert $marginTop="25px" $align="right">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
</MessageWrapper>
<BtnWrapper $gap="10px" $marginTop="10px" $marginBottom="20px">
<FormButtonContainer $gap="10px">
<>
<Button text="취소" theme="line" handleClick={() => handleSubmit('cancel')} />
<Button
type="submit"
text={isView() ? "예약 삭제": "등록"}
name={isView() ? "삭제버튼": "등록버튼"}
theme={
checkCondition()
? 'primary'
: 'disable'
}
handleClick={() => handleSubmit('submit')}
/>
</>
</FormButtonContainer>
</BtnWrapper>
</Modal>
{/* 확인 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.registConfirmModal}
modalText={isView() ? t('LAND_OWNED_CHANGES_SELECT_DELETE') : t('LAND_OWNED_CHANGES_REGIST_CONFIRM')}
handleSubmit={() => handleSubmit('registConfirm')}
handleCancel={() => handleModalClose('registConfirm')}
/>
{/* 완료 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.registCompleteModal}
modalText={isView() ? t('CANCEL_COMPLETED') : t('REGIST_COMPLTE')}
handleSubmit={() => handleSubmit('registComplete')}
/>
{/* 취소 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.cancelModal}
modalText={t('CANCEL_CONFIRM')}
handleCancel={() => handleModalClose('cancel')}
handleSubmit={() => handleSubmit('cancelConfirm')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleSubmit('warning')}
/>
{loading && <Loading/>}
</>
);
};
const initData = {
is_reserve: false,
land_id: '',
land_name: '',
user_guid: '',
user_name: '',
reservation_dt: ''
}
export default OwnerChangeModal;

View File

@@ -1,9 +1,9 @@
import { styled } from 'styled-components'; import { styled } from 'styled-components';
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../styles/Components'; import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../../styles/Components';
import Button from '../common/button/Button'; import Button from '../../common/button/Button';
import CheckBox from '../common/input/CheckBox'; import CheckBox from '../../common/input/CheckBox';
import { SearchBarLayout } from '../common/SearchBar'; import { SearchBarLayout } from '../../common/SearchBar';
import { useState } from 'react'; import { useState } from 'react';
const AdminViewSearchBar = ({ handleSearch, groupList, setResultData, setCurrentPage }) => { const AdminViewSearchBar = ({ handleSearch, groupList, setResultData, setCurrentPage }) => {

View File

@@ -230,15 +230,8 @@ const BattleEventSearchBar = ({ searchParams, onSearch, onReset, configData, rew
handleEndDate={date => onSearch({ endDate: date }, false)} handleEndDate={date => onSearch({ endDate: date }, false)}
/> />
</>, </>,
<></>,<></>,
<>
<BtnWrapper $gap="8px">
<Button theme="reset" handleClick={onReset} type="button" />
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
</BtnWrapper>
</>,
]; ];
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />; return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
}; };
export default BattleEventSearchBar; export default BattleEventSearchBar;

View File

@@ -0,0 +1,213 @@
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
import { useCallback, useEffect, useState } from 'react';
import { logAction, logDomain, userSearchType2 } from '../../../assets/data/options';
import { BusinessLogList } from '../../../apis/Log';
import { useTranslation } from 'react-i18next';
import {SearchFilter} from '../';
export const useBusinessLogSearch = (token, initialPageSize, setAlertMsg) => {
const { t } = useTranslation();
const [searchParams, setSearchParams] = useState({
search_type: 'GUID',
search_data: '',
log_action: 'None',
log_domain: 'BASE',
tran_id: '',
start_dt: (() => {
const date = new Date();
date.setDate(date.getDate() - 1);
return date;
})(),
end_dt: (() => {
const date = new Date();
date.setDate(date.getDate() - 1);
return date;
})(),
filters: [],
order_by: 'ASC',
page_size: initialPageSize,
page_no: 1
});
const [loading, setLoading] = useState(false);
const [data, setData] = useState(null);
useEffect(() => {
//초기 데이터 로드 안함
// const initialLoad = async () => {
// await fetchData(searchParams);
// };
// initialLoad();
}, [token]);
const fetchData = useCallback(async (params) => {
if (!token) return;
try {
setLoading(true);
const result = await BusinessLogList(
token,
params
);
if(result.result === "ERROR_LOG_MEMORY_LIMIT"){
setAlertMsg(t('LOG_MEMORY_LIMIT_WARNING'))
}else if(result.result === "ERROR_MONGODB_QUERY"){
setAlertMsg(t('LOG_MONGGDB_QUERY_WARNING'))
}
setData(result.data);
return result.data;
} catch (error) {
console.error('Error fetching auction data:', error);
throw error;
} finally {
setLoading(false);
}
}, [token]);
const updateSearchParams = useCallback((newParams) => {
setSearchParams(prev => ({
...prev,
...newParams
}));
}, []);
const handleSearch = useCallback(async (newParams = {}, executeSearch = true) => {
const updatedParams = {
...searchParams,
...newParams,
page_no: newParams.page_no || 1 // Reset to first page on new search
};
updateSearchParams(updatedParams);
if (executeSearch) {
return await fetchData(updatedParams);
}
return null;
}, [searchParams, fetchData]);
const handleReset = useCallback(async () => {
const now = new Date();
now.setDate(now.getDate() - 1);
const resetParams = {
search_type: 'GUID',
search_data: '',
log_action: 'None',
log_domain: 'BASE',
tran_id: '',
start_dt: now,
end_dt: now,
filters: [],
order_by: 'ASC',
page_size: initialPageSize,
page_no: 1
};
setSearchParams(resetParams);
return await fetchData(resetParams);
}, [initialPageSize, fetchData]);
const handlePageChange = useCallback(async (newPage) => {
return await handleSearch({ page_no: newPage }, true);
}, [handleSearch]);
const handlePageSizeChange = useCallback(async (newSize) => {
return await handleSearch({ page_size: newSize, page_no: 1 }, true);
}, [handleSearch]);
const handleOrderByChange = useCallback(async (newOrder) => {
return await handleSearch({ order_by: newOrder }, true);
}, [handleSearch]);
return {
searchParams,
loading,
data,
handleSearch,
handleReset,
handlePageChange,
handlePageSizeChange,
handleOrderByChange,
updateSearchParams
};
};
const BusinessLogSearchBar = ({ searchParams, onSearch, onReset }) => {
const handleSubmit = event => {
event.preventDefault();
onSearch(searchParams, true);
};
const searchList = [
<>
<InputGroup>
<SelectInput value={searchParams.search_type} onChange={e => onSearch({search_type: e.target.value }, false)}>
{userSearchType2.map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput>
<TextInput
type="text"
placeholder={searchParams.search_type === 'GUID' ? 'GUID ID 입력' : searchParams.search_type === 'NICKNAME' ? '아바타명 입력' :'Account ID 입력'}
value={searchParams.search_data}
width="260px"
onChange={e => onSearch({ search_data: e.target.value }, false)}
/>
</InputGroup>
</>,
<>
<InputLabel>로그액션</InputLabel>
<SelectInput value={searchParams.log_action} onChange={e => onSearch({ log_action: e.target.value }, false)} >
{logAction.map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput>
</>,
<>
<InputLabel>로그도메인</InputLabel>
<SelectInput value={searchParams.log_domain} onChange={e => onSearch({ log_domain: e.target.value }, false)} >
{logDomain.map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput>
</>,
];
const optionList = [
<>
<InputLabel>트랜잭션 ID</InputLabel>
<TextInput
type="text"
placeholder='트랜잭션 ID 입력'
value={searchParams.tran_id}
width="300px"
onChange={e => onSearch({ tran_id: e.target.value }, false)}
/>
</>,
<>
<InputLabel>일자</InputLabel>
<SearchPeriod
startDate={searchParams.start_dt}
handleStartDate={date => onSearch({ start_dt: date }, false)}
endDate={searchParams.end_dt}
handleEndDate={date => onSearch({ end_dt: date }, false)}
/>
</>,
];
const filterComponent = (
<SearchFilter value={searchParams.filters} onChange={e => onSearch({filters: e.target.value }, false)} />
);
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} filter={filterComponent} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
};
export default BusinessLogSearchBar;

View File

@@ -72,15 +72,9 @@ const CaliumRequestSearchBar = ({ handleSearch, setResultData }) => {
))} ))}
</SelectInput> </SelectInput>
</>, </>,
<>
<BtnWrapper $gap="8px">
<Button theme="reset" handleClick={handleReset} type="button" />
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
</BtnWrapper>
</>,
]; ];
return <SearchBarLayout firstColumnData={searchList} direction={'column'} />; return <SearchBarLayout firstColumnData={searchList} direction={'column'} onReset={handleReset} handleSubmit={handleSubmit} />;
}; };
export default CaliumRequestSearchBar; export default CaliumRequestSearchBar;

View File

@@ -0,0 +1,125 @@
import { BtnWrapper, InputLabel, TextInput } from '../../../styles/Components';
import Button from '../../common/button/Button';
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { InitHistoryList } from '../../../apis/Data';
export const useDataInitSearch = (token, setAlertMsg) => {
const { t } = useTranslation();
const [searchParams, setSearchParams] = useState({
tran_id: '',
start_dt: (() => {
return new Date();
})(),
end_dt: (() => {
return new Date();
})(),
});
const [loading, setLoading] = useState(false);
const [data, setData] = useState(null);
useEffect(() => {
const initialLoad = async () => {
await fetchData(searchParams);
};
initialLoad();
}, [token]);
const fetchData = useCallback(async (params) => {
if (!token) return;
try {
setLoading(true);
const result = await InitHistoryList(
token,
params
);
if(result.result === "ERROR_LOG_MEMORY_LIMIT"){
setAlertMsg(t('LOG_MEMORY_LIMIT_WARNING'))
}else if(result.result === "ERROR_MONGODB_QUERY"){
setAlertMsg(t('LOG_MONGGDB_QUERY_WARNING'))
}
setData(result.data);
return result.data;
} catch (error) {
console.error('Error fetching auction data:', error);
throw error;
} finally {
setLoading(false);
}
}, [token]);
const updateSearchParams = useCallback((newParams) => {
setSearchParams(prev => ({
...prev,
...newParams
}));
}, []);
const handleSearch = useCallback(async (newParams = {}) => {
const updatedParams = {
...searchParams,
...newParams,
};
updateSearchParams(updatedParams);
return await fetchData(updatedParams);
}, [searchParams, fetchData]);
const handleReset = useCallback(async () => {
const now = new Date();
const resetParams = {
tran_id: '',
start_dt: now,
end_dt: now,
};
setSearchParams(resetParams);
return await fetchData(resetParams);
}, [fetchData]);
return {
searchParams,
loading,
data,
handleSearch,
handleReset,
updateSearchParams
};
};
const DataInitSearchBar = ({ searchParams, onSearch, onReset }) => {
const handleSubmit = event => {
event.preventDefault();
onSearch(searchParams);
};
const searchList = [
<>
<InputLabel>트랜잭션 ID</InputLabel>
<TextInput
type="text"
placeholder='트랜잭션 ID 입력'
value={searchParams.tran_id}
width="300px"
onChange={e => onSearch({ tran_id: e.target.value })}
/>
</>,
<>
<InputLabel>일자</InputLabel>
<SearchPeriod
startDate={searchParams.start_dt}
handleStartDate={date => onSearch({ start_dt: date }, false)}
endDate={searchParams.end_dt}
handleEndDate={date => onSearch({ end_dt: date }, false)}
/>
</>,
];
return <SearchBarLayout firstColumnData={searchList} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
};
export default DataInitSearchBar;

View File

@@ -89,15 +89,8 @@ const EventListSearchBar = ({ handleSearch, setResultData }) => {
))} ))}
</SelectInput> </SelectInput>
</>, </>,
<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,<></>,
<>
<BtnWrapper $gap="8px">
<Button theme="reset" handleClick={handleReset} type="button" />
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
</BtnWrapper>
</>,
]; ];
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />; return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} onReset={handleReset} handleSubmit={handleSubmit} />;
}; };
export default EventListSearchBar; export default EventListSearchBar;

View File

@@ -114,14 +114,8 @@ const ItemsSearchBar = ({ handleSearch, setResultData }) => {
maxDate={new Date()} maxDate={new Date()}
/> />
</>, </>,
<>
<BtnWrapper $gap="8px">
<Button theme="reset" handleClick={handleReset} />
<Button theme="search" text="검색" type="submit" handleClick={handleSubmit} />
</BtnWrapper>
</>,
]; ];
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />; return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} onReset={handleReset} handleSubmit={handleSubmit} />;
}; };
export default ItemsSearchBar; export default ItemsSearchBar;

View File

@@ -191,15 +191,8 @@ const LandAuctionSearchBar = ({ searchParams, onSearch, onReset }) => {
handleEndDate={date => onSearch({ auctionEndDate: date }, false)} handleEndDate={date => onSearch({ auctionEndDate: date }, false)}
/> />
</>, </>,
<></>,<></>,
<>
<BtnWrapper $gap="8px">
<Button theme="reset" handleClick={onReset} type="button" />
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
</BtnWrapper>
</>,
]; ];
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />; return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
}; };
export default LandAuctionSearchBar; export default LandAuctionSearchBar;

View File

@@ -0,0 +1,194 @@
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
import Button from '../../common/button/Button';
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
import { useCallback, useEffect, useState } from 'react';
import { LandAuctionView, LandInfoData } from '../../../apis';
import { landAuctionStatus, landSearchType, landSize, opLandCategoryType } from '../../../assets/data';
import { opLandInfoStatusType } from '../../../assets/data/options';
export const useLandInfoSearch = (token, initialPageSize) => {
const [searchParams, setSearchParams] = useState({
landType: 'ID',
landData: '',
landSize: 'ALL',
category: 'ALL',
status: 'ALL',
startDate: '',
endDate: '',
orderBy: 'DESC',
pageSize: initialPageSize,
currentPage: 1
});
const [loading, setLoading] = useState(false);
const [data, setData] = useState(null);
useEffect(() => {
// fetchData(searchParams); // 컴포넌트 마운트 시 초기 데이터 로드
const initialLoad = async () => {
await fetchData(searchParams);
};
initialLoad();
}, [token]);
const fetchData = useCallback(async (params) => {
if (!token) return;
try {
setLoading(true);
const result = await LandInfoData(
token,
params.landType,
params.landData,
params.landSize,
params.category,
params.status,
params.startDate && new Date(params.startDate).toISOString(),
params.endDate && new Date(params.endDate).toISOString(),
params.orderBy,
params.pageSize,
params.currentPage
);
setData(result);
return result;
} catch (error) {
console.error('Error fetching auction data:', error);
throw error;
} finally {
setLoading(false);
}
}, [token]);
const updateSearchParams = useCallback((newParams) => {
setSearchParams(prev => ({
...prev,
...newParams
}));
}, []);
const handleSearch = useCallback(async (newParams = {}) => {
const updatedParams = {
...searchParams,
...newParams,
currentPage: newParams.currentPage || 1 // Reset to first page on new search
};
updateSearchParams(updatedParams);
return await fetchData(updatedParams);
}, [searchParams, fetchData]);
const handleReset = useCallback(async () => {
const resetParams = {
landType: 'ID',
landData: '',
landSize: 'ALL',
category: 'ALL',
status: 'ALL',
startDate: '',
endDate: '',
orderBy: 'DESC',
pageSize: initialPageSize,
currentPage: 1
};
setSearchParams(resetParams);
return await fetchData(resetParams);
}, [initialPageSize, fetchData]);
const handlePageChange = useCallback(async (newPage) => {
return await handleSearch({ currentPage: newPage });
}, [handleSearch]);
const handlePageSizeChange = useCallback(async (newSize) => {
return await handleSearch({ pageSize: newSize, currentPage: 1 });
}, [handleSearch]);
const handleOrderByChange = useCallback(async (newOrder) => {
return await handleSearch({ orderBy: newOrder });
}, [handleSearch]);
return {
searchParams,
loading,
data,
handleSearch,
handleReset,
handlePageChange,
handlePageSizeChange,
handleOrderByChange,
updateSearchParams
};
};
const LandInfoSearchBar = ({ searchParams, onSearch, onReset }) => {
const handleSubmit = event => {
event.preventDefault();
onSearch(searchParams);
};
const searchList = [
<>
<InputGroup>
<SelectInput value={searchParams.landType} onChange={e => onSearch({landType: e.target.value })}>
{landSearchType.map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput>
<TextInput
type="text"
placeholder={searchParams.landType === 'ID' ? '랜드 ID 입력' : '랜드명 입력'}
value={searchParams.landData}
width="300px"
onChange={e => onSearch({ landData: e.target.value })}
/>
</InputGroup>
</>,
<>
<InputLabel>랜드크기</InputLabel>
<SelectInput value={searchParams.landSize} onChange={e => onSearch({ landSize: e.target.value }, false)} >
{landSize.map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput>
</>,
<>
<InputLabel>랜드상태</InputLabel>
<SelectInput value={searchParams.status} onChange={e => onSearch({ status: e.target.value }, false)} >
{opLandInfoStatusType.map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput>
</>,
];
const optionList = [
<>
<InputLabel>카테고리</InputLabel>
<SelectInput value={searchParams.category} onChange={e => onSearch({ category: e.target.value }, false)}>
{opLandCategoryType.map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput>
</>,
<>
<InputLabel>일자</InputLabel>
<SearchPeriod
startDate={searchParams.startDate}
handleStartDate={date => onSearch({ startDate: date }, false)}
endDate={searchParams.endDate}
handleEndDate={date => onSearch({ endDate: date }, false)}
/>
</>,
];
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
};
export default LandInfoSearchBar;

View File

@@ -1,9 +1,9 @@
import { styled } from 'styled-components'; import { styled } from 'styled-components';
import { useState } from 'react'; import { useState } from 'react';
import { TextInput, InputLabel, SelectInput, BtnWrapper } from '../../styles/Components'; import { TextInput, InputLabel, SelectInput, BtnWrapper } from '../../../styles/Components';
import Button from '../common/button/Button'; import Button from '../../common/button/Button';
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar'; import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
const LogViewSearchBar = ({ handleSearch, resultData }) => { const LogViewSearchBar = ({ handleSearch, resultData }) => {
const [searchData, setSearchData] = useState({ const [searchData, setSearchData] = useState({

View File

@@ -130,14 +130,8 @@ const MailListSearchBar = ({ handleSearch, setResultData }) => {
))} ))}
</SelectInput> </SelectInput>
</>, </>,
<>
<BtnWrapper $gap="8px">
<Button theme="reset" handleClick={handleReset} type="button" />
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
</BtnWrapper>
</>,
]; ];
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />; return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} onReset={handleReset} handleSubmit={handleSubmit} />;
}; };
export default MailListSearchBar; export default MailListSearchBar;

View File

@@ -123,14 +123,8 @@ const ReportListSearchBar = ({ handleSearch, setResultData }) => {
<TextInput placeholder="입력" onChange={e => setSearchData({ ...searchData, searchKey: e.target.value })} /> <TextInput placeholder="입력" onChange={e => setSearchData({ ...searchData, searchKey: e.target.value })} />
</InputGroup> </InputGroup>
</>, </>,
<>
<BtnWrapper $gap="8px">
<Button theme="reset" handleClick={handleReset} type="button" />
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
</BtnWrapper>
</>,
]; ];
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />; return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} onReset={handleReset} handleSubmit={handleSubmit} />;
}; };
export default ReportListSearchBar; export default ReportListSearchBar;

View File

@@ -0,0 +1,238 @@
import { useEffect, useState } from 'react';
import { styled } from 'styled-components';
import { TextInput, InputLabel, SelectInput } from '../../../styles/Components';
import { logDomain, opInputType } from '../../../assets/data/options';
const SearchFilter = ({ value = [], onChange }) => {
const [filters, setFilters] = useState(value || []);
const [filterSections, setFilterSections] = useState([{ field_name: '', field_type: 'String', value: '' }]);
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
setFilters(value || []);
}, [value]);
const handleInputChange = (index, field, inputValue) => {
const updatedSections = [...filterSections];
updatedSections[index] = { ...updatedSections[index], [field]: inputValue };
setFilterSections(updatedSections);
};
const handleAddFilter = (index) => {
const filterToAdd = filterSections[index];
// Only add if both field_name and value are provided
if (filterToAdd.field_name && filterToAdd.value && filterToAdd.field_type) {
const updatedFilters = [...filters, { field_name: filterToAdd.field_name, field_type: filterToAdd.field_type, value: filterToAdd.value }];
setFilters(updatedFilters);
onChange({ target: { value: updatedFilters } });
// Reset this section
const updatedSections = [...filterSections];
updatedSections[index] = { field_name: '', field_type: 'String', value: '' };
setFilterSections(updatedSections);
}
};
const handleAddFilterSection = () => {
setFilterSections([...filterSections, { field_name: '', field_type: 'String', value: '' }]);
};
const handleRemoveFilterSection = (index) => {
if (filterSections.length > 1) {
const updatedSections = filterSections.filter((_, i) => i !== index);
setFilterSections(updatedSections);
}
};
const handleRemoveFilter = (index) => {
const updatedFilters = filters.filter((_, i) => i !== index);
setFilters(updatedFilters);
onChange({ target: { value: updatedFilters } });
};
const toggleFilters = () => {
setIsOpen(!isOpen);
};
return (
<FilterWrapper>
<FilterToggle onClick={toggleFilters}>
필터 {isOpen ? '▲' : '▼'}
</FilterToggle>
{isOpen && (
<>
{filters.length > 0 && (
<FilterSection>
{filters.map((filter, index) => (
<FilterItem key={index}>
<FilterName>{filter.field_name}:</FilterName>
<FilterValue>{filter.value}</FilterValue>
<RemoveButton onClick={() => handleRemoveFilter(index)}>×</RemoveButton>
</FilterItem>
))}
</FilterSection>
)}
{filterSections.map((section, index) => (
<FilterInputSection key={index}>
<InputLabel>속성 이름</InputLabel>
<TextInput
type="text"
placeholder="속성 이름 입력"
value={section.field_name}
onChange={(e) => handleInputChange(index, 'field_name', e.target.value)}
/>
<InputLabel>속성 유형</InputLabel>
<SelectInput
value={section.field_type}
onChange={(e) => handleInputChange(index, 'field_type', e.target.value)}
>
{opInputType.map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput>
<InputLabel>속성 </InputLabel>
<TextInput
type="text"
placeholder="속성 값 입력"
value={section.value}
onChange={(e) => handleInputChange(index, 'value', e.target.value)}
/>
<AddButton onClick={() => handleAddFilter(index)}>추가</AddButton>
{filterSections.length > 1 && (
<RemoveButton onClick={() => handleRemoveFilterSection(index)}>×</RemoveButton>
)}
</FilterInputSection>
))}
{/*<AddFilterButton onClick={handleAddFilterSection}>*/}
{/* 필터 추가*/}
{/*</AddFilterButton>*/}
</>
)}
</FilterWrapper>
);
};
export default SearchFilter;
const FilterWrapper = styled.div`
width: 100%;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
`;
const FilterToggle = styled.div`
width: 100%;
padding: 10px 15px;
cursor: pointer;
font-weight: 500;
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
&:hover {
background: #e5e5e5;
}
`;
const FilterSection = styled.div`
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 15px;
`;
const FilterItem = styled.div`
display: flex;
align-items: center;
background: #f0f0f0;
border-radius: 4px;
padding: 6px 10px;
font-size: 13px;
`;
const FilterName = styled.span`
font-weight: 500;
margin-right: 5px;
`;
const FilterValue = styled.span`
color: #333;
`;
const RemoveButton = styled.button`
background: none;
border: none;
color: #888;
font-size: 16px;
cursor: pointer;
margin-left: 8px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
&:hover {
color: #ff4d4f;
}
`;
const AddButton = styled.button.attrs({
type: 'button'
})`
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
font-size: 13px;
&:hover {
background: #40a9ff;
}
`;
const FilterInputSection = styled.div`
display: flex;
padding: 15px;
margin: 0 15px 15px;
border-radius: 6px;
gap: 10px;
align-items: center;
${TextInput} {
width: 160px;
margin-right: 5px;
}
`;
const AddFilterButton = styled.button.attrs({
type: 'button'
})`
border: 1px dashed #ccc;
border-radius: 4px;
padding: 8px 15px;
margin: 0 15px 15px;
cursor: pointer;
font-size: 13px;
width: calc(100% - 30px);
text-align: center;
&:hover {
background: #e5e5e5;
}
`;

View File

@@ -104,14 +104,8 @@ const UserBlockSearchBar = ({ handleSearch, setResultData }) => {
))} ))}
</SelectInput> </SelectInput>
</>, </>,
<>
<BtnWrapper $gap="8px">
<Button theme="reset" handleClick={handleReset} />
<Button theme="search" text="검색" type="submit" handleClick={handleSubmit} />
</BtnWrapper>
</>,
]; ];
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} />; return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} onReset={handleReset} handleSubmit={handleSubmit} />;
}; };
export default UserBlockSearchBar; export default UserBlockSearchBar;

View File

@@ -9,37 +9,41 @@ export const UserInfoSkeleton = () => {
<SkeletonImg width="200px" height="150px" /> <SkeletonImg width="200px" height="150px" />
</ProfileWrapper> </ProfileWrapper>
<UserInfoTable> <UserInfoTable>
<Skeleton width="530px" height="30px" /> <tbody>
<Skeleton width="530px" height="30px" /> {Array.from({ length: 10 }).map((_, index) => (
<Skeleton width="530px" height="30px" /> <tr key={index}>
<Skeleton width="530px" height="30px" /> <td>
<Skeleton width="530px" height="30px" /> <Skeleton width="530px" height="30px" />
<Skeleton width="530px" height="30px" /> </td>
<Skeleton width="530px" height="30px" /> </tr>
<Skeleton width="530px" height="30px" /> ))}
<Skeleton width="530px" height="30px" /> </tbody>
<Skeleton width="530px" height="30px" />
</UserInfoTable> </UserInfoTable>
</UserDefault> </UserDefault>
<UserInfoTable> <UserInfoTable>
<Skeleton width="750px" height="30px" /> <tbody>
<Skeleton width="750px" height="30px" /> {Array.from({ length: 4 }).map((_, index) => (
<Skeleton width="750px" height="30px" /> <tr key={index}>
<Skeleton width="750px" height="30px" /> <td>
<Skeleton width="750px" height="30px" />
</td>
</tr>
))}
</tbody>
</UserInfoTable> </UserInfoTable>
</> </>
) )
} }
const UserDefault = styled.div` const UserDefault = styled.div`
display: flex; display: flex;
gap: 40px; gap: 40px;
margin-bottom: 30px; margin-bottom: 30px;
justify-content: space-between; justify-content: space-between;
`; `;
const ProfileWrapper = styled.div` const ProfileWrapper = styled.div`
width: 150px; width: 150px;
height: 150px; height: 150px;
border-radius: 75px; border-radius: 75px;
overflow: hidden; overflow: hidden;
display: inline-flex; display: inline-flex;

View File

@@ -1,86 +0,0 @@
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../styles/Components';
import Button from '../common/button/Button';
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
import { useState } from 'react';
import { caliumStatus } from '../../assets/data/options';
const CaliumRequestSearchBar = ({ handleSearch, setResultData }) => {
const [searchData, setSearchData] = useState({
content: '',
status: 'ALL',
startDate: '',
endDate: '',
});
const handleSubmit = event => {
event.preventDefault();
handleSearch(
searchData.content,
searchData.status ? searchData.status : 'ALL',
searchData.startDate ? searchData.startDate : '',
searchData.endDate ? searchData.endDate : new Date(),
(searchData.startDate && searchData.endDate === '') && setSearchData({ startDate : searchData.startDate ,endDate : new Date()}),
);
setResultData(searchData);
};
const handleReset = () => {
setSearchData({
content: '',
status: 'ALL',
startDate: '',
endDate: '',
order: 'DESC',
});
handleSearch('', 'ALL', '', '');
setResultData('', 'ALL', '', '');
window.location.reload();
};
const searchList = [
<>
<InputLabel>등록 일자</InputLabel>
<SearchPeriod
startDate={searchData.startDate}
handleStartDate={data => {
setSearchData({ ...searchData, startDate: data });
}}
endDate={searchData.endDate}
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
/>
</>,
<>
<InputLabel>요청 내용</InputLabel>
<TextInput
type="text"
placeholder="요청 내용"
value={searchData.content}
onChange={e => setSearchData({ ...searchData, content: e.target.value })}
/>
</>,
<>
<InputLabel>요청 상태</InputLabel>
<SelectInput value={searchData.status} onChange={e => setSearchData({ ...searchData, status: e.target.value })}>
{caliumStatus.map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput>
</>,
<>
<BtnWrapper $gap="8px">
<Button theme="reset" handleClick={handleReset} type="button" />
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
</BtnWrapper>
</>,
];
return <SearchBarLayout firstColumnData={searchList} direction={'column'} />;
};
export default CaliumRequestSearchBar;

View File

@@ -1,17 +1,11 @@
import CaliumRequestSearchBar from './CaliumRequestSearchBar';
import CaliumRequestRegistModal from './CaliumRequestRegistModal' import CaliumRequestRegistModal from './CaliumRequestRegistModal'
import AdminViewSearchBar from './AdminViewSearchBar'
import AuthRegistBar from './AuthRegistBar' import AuthRegistBar from './AuthRegistBar'
import LogViewModal from './LogViewModal' import LogViewModal from './LogViewModal'
import LogViewSearchBar from './LogViewSearchBar'
import AuthGroupRows from './AuthGroupRows' import AuthGroupRows from './AuthGroupRows'
export { export {
CaliumRequestSearchBar,
CaliumRequestRegistModal, CaliumRequestRegistModal,
AdminViewSearchBar,
AuthRegistBar, AuthRegistBar,
LogViewSearchBar,
LogViewModal, LogViewModal,
AuthGroupRows AuthGroupRows
} }

View File

@@ -0,0 +1,75 @@
import React from 'react';
import styled from 'styled-components';
// 원형 프로그레스 컴포넌트
const CircularProgress = ({
progress,
size = 40,
strokeWidth,
backgroundColor = '#E0E0E0',
progressColor = '#4A90E2',
textColor = '#4A90E2',
showText = true,
textSize,
className
}) => {
// 기본값 계산
const actualStrokeWidth = strokeWidth || size * 0.1; // 프로그레스 바 두께
const radius = (size - actualStrokeWidth) / 2; // 원의 반지름
const circumference = 2 * Math.PI * radius; // 원의 둘레
const strokeDashoffset = circumference - (progress / 100) * circumference; // 진행률에 따른 offset 계산
const actualTextSize = textSize || Math.max(10, size * 0.3); // 텍스트 크기
return (
<ProgressContainer size={size} className={className}>
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
{/* 배경 원 */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke={backgroundColor}
strokeWidth={actualStrokeWidth}
/>
{/* 진행률 표시 원 */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke={progressColor}
strokeWidth={actualStrokeWidth}
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round"
transform={`rotate(-90 ${size / 2} ${size / 2})`}
/>
</svg>
{showText && (
<ProgressText color={textColor} fontSize={actualTextSize}>
{`${Math.round(progress)}%`}
</ProgressText>
)}
</ProgressContainer>
);
};
export default CircularProgress;
// 스타일 컴포넌트
const ProgressContainer = styled.div`
position: relative;
width: ${props => props.size}px;
height: ${props => props.size}px;
display: flex;
align-items: center;
justify-content: center;
`;
const ProgressText = styled.div`
position: absolute;
font-size: ${props => props.fontSize}px;
font-weight: bold;
color: ${props => props.color};
`;

View File

@@ -52,7 +52,7 @@ const SingleTimePicker = ({
return ( return (
<> <>
<FormLabel>{label}</FormLabel> {label && <FormLabel>{label}</FormLabel>}
<TimeContainer> <TimeContainer>
<StyledSelectInput <StyledSelectInput
onChange={handleTimeChange} onChange={handleTimeChange}

View File

@@ -0,0 +1,42 @@
import styled from 'styled-components';
const DownloadProgress = ({ progress }) => {
return (
<ProgressWrapper>
<ProgressText>다운로드 ... {progress}%</ProgressText>
<ProgressBarContainer>
<ProgressBarFill style={{ width: `${progress}%` }} />
</ProgressBarContainer>
</ProgressWrapper>
);
};
export default DownloadProgress;
const ProgressWrapper = styled.div`
margin: 10px 0;
padding: 10px;
background-color: #f9f9f9;
border-radius: 4px;
border: 1px solid #ddd;
`;
const ProgressText = styled.div`
margin-bottom: 5px;
text-align: center;
font-weight: bold;
color: #333;
`;
const ProgressBarContainer = styled.div`
height: 20px;
background-color: #e0e0e0;
border-radius: 10px;
overflow: hidden;
`;
const ProgressBarFill = styled.div`
height: 100%;
background-color: #4caf50;
transition: width 0.3s ease;
`;

View File

@@ -9,9 +9,8 @@ import { useEffect, useState } from 'react';
import Button from '../button/Button'; import Button from '../button/Button';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { AuthInfo } from '../../../apis'; import { AuthInfo } from '../../../apis';
import { authType } from '../../../assets/data';
import { menuConfig } from '../../../assets/data/menuConfig';
import { getMenuConfig } from '../../../utils'; import { getMenuConfig } from '../../../utils';
import { adminAuthLevel } from '../../../assets/data/types';
const Navi = () => { const Navi = () => {
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
@@ -71,54 +70,16 @@ const Navi = () => {
} }
}; };
// const menu = [ const isClickable = (submenu) => {
// { switch (userInfo.auth_level_type) {
// title: '운영자 관리', case adminAuthLevel.DEVELOPER:
// link: '/usermanage', case adminAuthLevel.READER:
// access: userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.adminSearchRead || auth.id === authType.adminLogSearchRead || auth.id === authType.authoritySettingRead || auth.id === authType.caliumRequestRead), case adminAuthLevel.MASTER:
// submenu: [ return true;
// { title: '운영자 조회', link: '/usermanage/adminview', id: authType.adminSearchRead }, default:
// { title: '사용 이력 조회', link: '/usermanage/logview', id: authType.adminLogSearchRead }, return submenu.authLevel === adminAuthLevel.NONE && userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === submenu.id);
// { title: '권한 설정', link: '/usermanage/authsetting', id: authType.authoritySettingRead }, }
// { title: '칼리움 요청', link: '/usermanage/caliumrequest', id: authType.caliumRequestRead }, }
// ],
// },
// {
// title: '지표 관리',
// link: '/indexmanage',
// access: userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.userIndicatorsRead || auth.id === authType.economicIndicatorsRead),
// submenu: [
// { title: '유저 지표', link: '/indexmanage/userindex', id: authType.userIndicatorsRead },
// { title: '경제 지표', link: '/indexmanage/economicindex', id: authType.economicIndicatorsRead },
// ],
// },
// {
// title: '운영 정보 관리',
// link: '/datamanage',
// access: userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.userSearchRead || auth.id === authType.contentSearchRead || auth.id === authType.gameLogRead || auth.id === authType.cryptoRead),
// submenu: [
// { title: '유저 조회', link: '/datamanage/userview', id: authType.userIndicatorsRead },
// { title: '컨텐츠 조회', link: '/datamanage/contentsview', id: authType.contentSearchRead },
// { title: '게임 로그 조회', link: '/datamanage/gamelogview', id: authType.gameLogRead },
// { title: '크립토 조회', link: '/datamanage/cryptview', id: authType.cryptoRead },
// ],
// },
// {
// title: '운영 서비스 관리',
// link: '/servicemanage',
// access: userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.inGameRead || auth.id === authType.whiteListRead || auth.id === authType.mailRead
// || auth.id === authType.blackListRead || auth.id === authType.reportRead || auth.id === authType.itemRead || auth.id === authType.eventRead ),
// submenu: [
// { title: '인게임 메시지', link: '/servicemanage/board', id: authType.inGameRead },
// // { title: '화이트리스트', link: '/servicemanage/whitelist', id: authType.whiteListRead },
// { title: '우편', link: '/servicemanage/mail', id: authType.mailRead },
// { title: '이용자 제재', link: '/servicemanage/userblock', id: authType.blackListRead },
// { title: '신고내역', link: '/servicemanage/reportlist', id: authType.reportRead },
// // { title: '아이템 복구 및 삭제', link: '/servicemanage/items', id: authType.itemRead },
// { title: '보상 이벤트 관리', link: '/servicemanage/event', id: authType.eventRead },
// ],
// },
// ];
return ( return (
<> <>
@@ -133,14 +94,14 @@ const Navi = () => {
</TopMenu> </TopMenu>
)} )}
<SubMenu> <SubMenu>
{item.submenu && {item.submenu && userInfo &&
item.submenu.map((submenu, idx) => { item.submenu.map((submenu, idx) => {
return ( return (
<SubMenuItem key={idx} $isclickable={userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === submenu.id) ? 'true' : 'false'}> <SubMenuItem key={idx} $isclickable={isClickable(submenu) ? 'true' : 'false'}>
<NavLink <NavLink
to={userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === submenu.id) ? submenu.link : location.pathname} to={isClickable(submenu) ? submenu.link : location.pathname}
onClick={e => { onClick={e => {
userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === submenu.id) ? handleLink(e) : handleModalClose(); isClickable(submenu) ? handleLink(e) : handleModalClose();
}}> }}>
{submenu.title} {submenu.title}
</NavLink> </NavLink>

View File

@@ -29,7 +29,9 @@ const Profile = () => {
const fetchData = async () => { const fetchData = async () => {
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
setInfoData(await AuthInfo(token)); await AuthInfo(token).then(data => {
setInfoData(data);
});
}; };
useEffect(() => { useEffect(() => {

View File

@@ -0,0 +1,56 @@
import React from 'react';
import styled from 'styled-components';
const DynamoPagination = ({
pagination,
onNextPage,
onPrevPage,
className
}) => {
return (
<PaginationButtons className={className}>
<PaginationButton
onClick={onPrevPage}
disabled={pagination.currentPage === 1}
>
이전
</PaginationButton>
<PageInfo>{pagination.currentPage}</PageInfo>
<PaginationButton
onClick={onNextPage}
disabled={!pagination.hasNextPage}
>
다음
</PaginationButton>
</PaginationButtons>
);
};
const PaginationButtons = styled.div`
display: flex;
align-items: center;
gap: 10px;
`;
const PaginationButton = styled.button`
background-color: ${props => props.disabled ? '#e0e0e0' : '#6c7eb7'};
color: ${props => props.disabled ? '#a0a0a0' : 'white'};
border: none;
padding: 6px 12px;
border-radius: 4px;
font-size: 12px;
cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
transition: background-color 0.2s;
&:hover {
background-color: ${props => props.disabled ? '#e0e0e0' : '#5a6a9b'};
}
`;
const PageInfo = styled.div`
font-size: 12px;
font-weight: 500;
color: #666;
`;
export default DynamoPagination;

View File

@@ -0,0 +1,151 @@
import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import PaginationIcon from '../../../assets/img/icon/icon-pagination.png';
const FrontPagination = ({
data, // 전체 데이터 배열
itemsPerPage, // 페이지당 표시할 항목 수
currentPage, // 현재 페이지
setCurrentPage, // 현재 페이지 설정 함수
pageLimit = 10, // 페이지 네비게이션에 표시할 페이지 수
onPageChange // 페이지 변경 시 호출될 콜백 함수 (선택 사항)
}) => {
const [blockNum, setBlockNum] = useState(0);
// 전체 페이지 수 계산
const totalItems = data?.length || 0;
const maxPage = Math.ceil(totalItems / itemsPerPage);
// 페이지 번호 배열 생성
const pageNumbers = [];
for (let i = 1; i <= maxPage; i++) {
pageNumbers.push(i);
}
// 현재 블록에 표시할 페이지 번호
const v = blockNum * pageLimit;
const pArr = pageNumbers.slice(v, pageLimit + v);
const processPageData = useCallback(() => {
if (!data || !Array.isArray(data) || data.length === 0) return [];
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
return data.slice(startIndex, endIndex);
}, [data, currentPage, itemsPerPage]);
useEffect(() => {
if (onPageChange) {
const pageData = processPageData();
onPageChange(pageData);
}
}, [processPageData, onPageChange]);
// itemsPerPage나 데이터가 변경되면 블록 초기화
useEffect(() => {
setBlockNum(0);
}, [itemsPerPage, totalItems]);
// 첫 페이지로 이동
const firstPage = () => {
setBlockNum(0);
setCurrentPage(1);
};
// 마지막 페이지로 이동
const lastPage = () => {
setBlockNum(Math.ceil(maxPage / pageLimit) - 1);
setCurrentPage(maxPage);
};
// 이전 페이지로 이동
const prePage = () => {
if (currentPage <= 1) return;
if (currentPage - 1 <= pageLimit * blockNum) {
setBlockNum(n => n - 1);
}
setCurrentPage(n => n - 1);
};
// 다음 페이지로 이동
const nextPage = () => {
if (currentPage >= maxPage) return;
if (pageLimit * (blockNum + 1) <= currentPage) {
setBlockNum(n => n + 1);
}
setCurrentPage(n => n + 1);
};
// 특정 페이지로 이동
const clickPage = number => {
setCurrentPage(number);
};
// 현재 표시 중인 항목 범위 계산 (예: "1-10 / 총 100개")
const startItem = totalItems === 0 ? 0 : (currentPage - 1) * itemsPerPage + 1;
const endItem = Math.min(currentPage * itemsPerPage, totalItems);
return (
<>
<PaginationWrapper>
<Button $position="0" onClick={firstPage} disabled={currentPage === 1} />
<Button $position="-20px" onClick={prePage} disabled={currentPage === 1} />
{pArr.map(number => (
<PageNum
$state={currentPage === number ? 'on' : ''}
key={number}
onClick={() => clickPage(number)}
>
{number}
</PageNum>
))}
<Button $position="-40px" onClick={nextPage} disabled={currentPage === maxPage} />
<Button $position="-60px" onClick={lastPage} disabled={currentPage === maxPage} />
</PaginationWrapper>
</>
);
};
export default FrontPagination;
const PaginationWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
margin-top: 10px;
margin-bottom: 20px;
`;
const Button = styled.button`
background: url('${PaginationIcon}') no-repeat;
background-position: ${props => props.$position} 0;
width: 20px;
height: 20px;
opacity: ${props => props.disabled ? 0.5 : 1};
cursor: ${props => props.disabled ? 'default' : 'pointer'};
&:hover {
background-position: ${props => props.$position} ${props => props.disabled ? '0' : '-20px'};
}
`;
const PageNum = styled.div`
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
color: ${props => (props.$state === 'on' ? '#2c2c2c' : '#CECECE')};
cursor: pointer;
&:hover {
color: #2c2c2c;
}
`;

View File

@@ -1,7 +1,8 @@
import { styled } from 'styled-components'; import { styled } from 'styled-components';
import { TextInput, SelectInput, SearchBarAlert } from '../../../styles/Components'; import { TextInput, SelectInput, SearchBarAlert, BtnWrapper } from '../../../styles/Components';
import Button from '../button/Button';
const SearchBarLayout = ({ firstColumnData, secondColumnData, direction }) => { const SearchBarLayout = ({ firstColumnData, secondColumnData, filter, direction, onReset, handleSubmit }) => {
return ( return (
<SearchbarStyle direction={direction}> <SearchbarStyle direction={direction}>
<SearchRow> <SearchRow>
@@ -16,6 +17,17 @@ const SearchBarLayout = ({ firstColumnData, secondColumnData, direction }) => {
))} ))}
</SearchRow> </SearchRow>
)} )}
{filter && (
<SearchRow>
{filter}
</SearchRow>
)}
<SearchRow>
<BtnWrapper $gap="8px">
<Button theme="search" text="검색" handleClick={handleSubmit} type="button" />
<Button theme="reset" handleClick={onReset} type="button" />
</BtnWrapper>
</SearchRow>
</SearchbarStyle> </SearchbarStyle>
); );
}; };
@@ -23,16 +35,16 @@ const SearchBarLayout = ({ firstColumnData, secondColumnData, direction }) => {
export default SearchBarLayout; export default SearchBarLayout;
const SearchbarStyle = styled.div` const SearchbarStyle = styled.div`
width: 100%; width: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
font-size: 14px; font-size: 14px;
padding: 20px; padding: 20px;
border-top: 1px solid #000; border-radius: 8px;
border-bottom: 1px solid #000; border: 1px solid #ddd;
margin: 0 0 40px; margin: 0 0 40px;
flex-flow: ${props => props.direction}; flex-flow: ${props => props.direction};
gap: ${props => (props.direction === 'column' ? '20px' : '20px 0')}; gap: ${props => (props.direction === 'column' ? '20px' : '20px 0')};
`; `;
const SearchItem = styled.div` const SearchItem = styled.div`
@@ -54,4 +66,10 @@ const SearchRow = styled.div`
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 20px 0; gap: 20px 0;
&:last-child {
border-top: 1px solid #e0e0e0;
padding-top: 15px;
margin-top: 15px;
}
`; `;

View File

@@ -7,7 +7,7 @@ import {
import { ViewTitleCountType } from '../../../assets/data'; import { ViewTitleCountType } from '../../../assets/data';
import { TitleItem, TitleItemLabel, TitleItemValue } from '../../../styles/ModuleComponents'; import { TitleItem, TitleItemLabel, TitleItemValue } from '../../../styles/ModuleComponents';
const ViewTableInfo = ({children, total, total_all, handleOrderBy, handlePageSize, countType = ViewTitleCountType.total}) => { const ViewTableInfo = ({children, total, total_all, orderType, handleOrderBy, pageType, handlePageSize, countType = ViewTitleCountType.total}) => {
return ( return (
<TableInfo> <TableInfo>
{total !== undefined && total_all !== undefined && {total !== undefined && total_all !== undefined &&
@@ -27,18 +27,44 @@ const ViewTableInfo = ({children, total, total_all, handleOrderBy, handlePageSiz
} }
</ListCount>} </ListCount>}
<ListOption> <ListOption>
<SelectInput className="input-select" onChange={e => handleOrderBy(e)}> <OrderBySelect orderType={orderType} handleOrderBy={handleOrderBy} />
<option value="DESC">내림차순</option> <PageSelect pageType={pageType} handlePageSize={handlePageSize} />
<option value="ASC">오름차순</option>
</SelectInput>
<SelectInput name="" id="" className="input-select" onChange={e => handlePageSize(e)}>
<option value="50">50</option>
<option value="100">100</option>
</SelectInput>
{children} {children}
</ListOption> </ListOption>
</TableInfo> </TableInfo>
); );
}; };
const OrderBySelect = ({orderType, handleOrderBy}) => {
return(
orderType === "asc" ?
<SelectInput className="input-select" onChange={e => handleOrderBy(e.target.value)}>
<option value="ASC">오름차순</option>
<option value="DESC">내림차순</option>
</SelectInput>
:
<SelectInput className="input-select" onChange={e => handleOrderBy(e.target.value)}>
<option value="DESC">내림차순</option>
<option value="ASC">오름차순</option>
</SelectInput>
);
}
const PageSelect = ({pageType, handlePageSize}) => {
return(
pageType === "B" ?
<SelectInput name="" id="" className="input-select" onChange={e => handlePageSize(e.target.value)}>
<option value="500">500</option>
<option value="1000">1000</option>
<option value="5000">5000</option>
<option value="10000">10000</option>
</SelectInput>
:
<SelectInput name="" id="" className="input-select" onChange={e => handlePageSize(e.target.value)}>
<option value="50">50</option>
<option value="100">100</option>
</SelectInput>
);
}
export default ViewTableInfo; export default ViewTableInfo;

View File

@@ -1,111 +1,506 @@
import * as XLSX from 'xlsx-js-style'; import * as XLSX from 'xlsx-js-style';
import { ExcelDownButton } from '../../../styles/ModuleComponents'; import { ExcelDownButton } from '../../../styles/ModuleComponents';
import { useCallback, useEffect, useState } from 'react';
const ExcelDownloadButton = ({ tableRef, data, fileName = 'download.xlsx', sheetName = 'Sheet1', onLoadingChange }) => {
const [isDownloading, setIsDownloading] = useState(false);
const [lastProgress, setLastProgress] = useState(0);
// 타임아웃 감지 및 처리
useEffect(() => {
let timeoutTimer;
if (isDownloading && lastProgress >= 95) {
// 최종 단계에서 타임아웃 감지 타이머 설정
timeoutTimer = setTimeout(() => {
// 진행 상태가 여전히 변하지 않았다면 타임아웃으로 간주
if (isDownloading && lastProgress >= 95) {
console.log("Excel download timeout detected, completing process");
setIsDownloading(false);
if (onLoadingChange) {
onLoadingChange({ loading: false, progress: 100 });
}
}
}, 15000); // 15초 타임아웃
}
return () => {
if (timeoutTimer) clearTimeout(timeoutTimer);
};
}, [isDownloading, lastProgress, onLoadingChange]);
const ExcelDownloadButton = ({ tableRef, fileName = 'download.xlsx', sheetName = 'Sheet1' }) => {
const isNumeric = (value) => { const isNumeric = (value) => {
// 숫자 또는 숫자 문자열인지 확인 // 숫자 또는 숫자 문자열인지 확인
return !isNaN(value) && !isNaN(parseFloat(value)); return !isNaN(value) && !isNaN(parseFloat(value));
}; };
const downloadExcel = () => { // 테두리 스타일 정의
try { const borderStyle = {
if (!tableRef.current) return; style: "thin",
color: { rgb: "000000" }
};
const tableElement = tableRef.current; // 기본 셀 스타일
const headerRows = tableElement.getElementsByTagName('thead')[0].getElementsByTagName('tr'); const baseCellStyle = {
const bodyRows = tableElement.getElementsByTagName('tbody')[0].getElementsByTagName('tr'); font: {
name: "맑은 고딕",
// 헤더 데이터 추출 sz: 11
const headers = Array.from(headerRows[0].cells).map(cell => cell.textContent); },
border: {
// 바디 데이터 추출 및 숫자 타입 처리 top: borderStyle,
const bodyData = Array.from(bodyRows).map(row => bottom: borderStyle,
Array.from(row.cells).map(cell => { left: borderStyle,
const value = cell.textContent; right: borderStyle
return isNumeric(value) ? parseFloat(value) : value;
})
);
// 워크북 생성
const wb = XLSX.utils.book_new();
// 테두리 스타일 정의
const borderStyle = {
style: "thin",
color: { rgb: "000000" }
};
// 스타일 정의
const centerStyle = {
font: {
name: "맑은 고딕",
sz: 11
},
alignment: {
horizontal: 'right',
vertical: 'right'
},
border: {
top: borderStyle,
bottom: borderStyle,
left: borderStyle,
right: borderStyle
}
};
const headerStyle = {
alignment: {
horizontal: 'center',
vertical: 'center'
},
fill: {
fgColor: { rgb: "d9e1f2" },
patternType: "solid"
}
};
// 데이터에 스타일 적용
const wsData = [
// 헤더 행
headers.map(h => ({
v: h,
s: headerStyle
})),
// 데이터 행들
...bodyData.map(row =>
row.map(cell => ({
v: cell,
s: centerStyle
}))
)
];
// 워크시트 생성
const ws = XLSX.utils.aoa_to_sheet(wsData);
// 열 너비 설정 (최소 8, 최대 50)
ws['!cols'] = headers.map((_, index) => {
const maxLength = Math.max(
headers[index].length * 2,
...bodyData.map(row => String(row[index] || '').length * 1.2)
);
return { wch: Math.max(8, Math.min(50, maxLength)) };
});
// 워크시트를 워크북에 추가
XLSX.utils.book_append_sheet(wb, ws, sheetName);
// 엑셀 파일 다운로드
XLSX.writeFile(wb, fileName);
} catch (error) {
console.error('Excel download failed:', error);
alert('엑셀 다운로드 중 오류가 발생했습니다.');
} }
}; };
// 헤더 스타일
const headerStyle = {
...baseCellStyle,
font: {
...baseCellStyle.font,
bold: true
},
alignment: {
horizontal: 'center',
vertical: 'center'
},
fill: {
fgColor: { rgb: "d9e1f2" },
patternType: "solid"
}
};
// 기본 데이터 셀 스타일
const dataStyle = {
...baseCellStyle,
alignment: {
horizontal: 'left',
vertical: 'center',
wrapText: true
}
};
const flattenObject = (obj, prefix = '') => {
return Object.keys(obj).reduce((acc, key) => {
const prefixedKey = prefix ? `${prefix}.${key}` : key;
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
Object.assign(acc, flattenObject(obj[key], prefixedKey));
} else if (Array.isArray(obj[key])) {
// 배열은 JSON 문자열로 변환
acc[prefixedKey] = JSON.stringify(obj[key]);
} else {
acc[prefixedKey] = obj[key];
}
return acc;
}, {});
};
const updateLoadingState = (newProgress) => {
setLastProgress(newProgress);
if (onLoadingChange && typeof onLoadingChange === 'function') {
onLoadingChange({loading: true, progress: newProgress});
}
};
const downloadTableExcel = async () => {
return new Promise((resolve, reject) => {
try {
if (!tableRef || !tableRef.current) {
reject(new Error('테이블 참조가 없습니다.'));
return;
}
// Worker에 전달할 데이터 추출
updateLoadingState(10);
// 메인 스레드에서 데이터 추출
const tableElement = tableRef.current;
const headerRows = tableElement.getElementsByTagName('thead')[0].getElementsByTagName('tr');
const bodyRows = tableElement.getElementsByTagName('tbody')[0].getElementsByTagName('tr');
// 일반 행만 포함 (상세 행 제외)
const normalBodyRows = Array.from(bodyRows).filter(row => {
const hasTdWithColspan = Array.from(row.cells).some(cell => cell.hasAttribute('colspan'));
return !hasTdWithColspan;
});
// 헤더 데이터 추출
const headers = Array.from(headerRows[0].cells).map(cell => cell.textContent);
// 바디 데이터 추출 및 숫자 타입 처리
const bodyData = normalBodyRows.map(row =>
Array.from(row.cells).map(cell => {
const value = cell.textContent;
return isNumeric(value) ? parseFloat(value) : value;
})
);
updateLoadingState(30);
// 큰 데이터셋 처리를 위해 setTimeout으로 이벤트 루프 차단 방지
setTimeout(() => {
try {
// 워크북 생성
const wb = XLSX.utils.book_new();
updateLoadingState(50);
// 처리는 여러 단계로 나누어 이벤트 루프 차단 최소화
setTimeout(() => {
try {
// 데이터에 스타일 적용
const wsData = [
// 헤더 행
headers.map(h => ({
v: h,
s: headerStyle
}))
];
// 데이터 행 추가 (메모리 사용량 최소화를 위해 별도 처리)
const chunkSize = 1000; // 한 번에 처리할 행 수
let currentIndex = 0;
function processDataChunk() {
updateLoadingState(50 + Math.floor((currentIndex / bodyData.length) * 30));
const end = Math.min(currentIndex + chunkSize, bodyData.length);
for (let i = currentIndex; i < end; i++) {
wsData.push(
bodyData[i].map(cell => ({
v: cell,
s: dataStyle
}))
);
}
currentIndex = end;
if (currentIndex < bodyData.length) {
// 아직 처리할 데이터가 남아있으면 다음 청크 처리 예약
setTimeout(processDataChunk, 0);
} else {
// 모든 데이터 처리 완료 후 워크시트 생성
finishExcelCreation(wsData, headers, wb);
}
}
// 첫 번째 청크 처리 시작
processDataChunk();
} catch (error) {
reject(error);
}
}, 0);
function finishExcelCreation(wsData, headers, wb) {
try {
updateLoadingState(80);
// 워크시트 생성
const ws = XLSX.utils.aoa_to_sheet(wsData);
// 열 너비 설정 (최소 8, 최대 50)
ws['!cols'] = headers.map((_, index) => {
// 데이터의 일부만 샘플링하여 열 너비 계산 (전체 계산 시 성능 문제)
const samplingSize = Math.min(bodyData.length, 500);
const sampledRows = [];
for (let i = 0; i < samplingSize; i++) {
const randomIndex = Math.floor(Math.random() * bodyData.length);
sampledRows.push(bodyData[randomIndex]);
}
const maxLength = Math.max(
headers[index].length * 2,
...sampledRows.map(row => {
if (row[index] === undefined) return 0;
return String(row[index] || '').length * 1.2;
})
);
return { wch: Math.max(8, Math.min(50, maxLength)) };
});
updateLoadingState(90);
// 최종 다운로드는 별도 타임아웃에서 수행하여 UI 업데이트 가능하게 함
setTimeout(() => {
try {
// 워크시트를 워크북에 추가
XLSX.utils.book_append_sheet(wb, ws, sheetName);
updateLoadingState(95);
// 파일 다운로드 전 마지막 UI 업데이트를 위한 지연
setTimeout(() => {
try {
// 엑셀 파일 다운로드
XLSX.writeFile(wb, fileName);
updateLoadingState(100);
resolve();
} catch (error) {
reject(error);
}
}, 100);
} catch (error) {
reject(error);
}
}, 0);
} catch (error) {
reject(error);
}
}
} catch (error) {
reject(error);
}
}, 0);
} catch (error) {
reject(error);
}
});
};
const chunkArray = (array, chunkSize) => {
const chunks = [];
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize));
}
return chunks;
};
const processDataChunk = async (chunk, headers, allDataRows, processedCount, totalCount) => {
return new Promise((resolve) => {
setTimeout(() => {
// 각 청크의 데이터를 처리
const rowsData = chunk.map(item => {
return headers.map(header => {
const value = item[header] !== undefined ? item[header] : '';
return {
v: value,
s: dataStyle
};
});
});
// 진행률 계산 및 콜백 호출
const newProgress = Math.min(95, Math.round(((processedCount + chunk.length) / totalCount) * 80) + 15);
updateLoadingState(newProgress);
// 처리된 데이터 행들을 전체 데이터 배열에 추가
allDataRows.push(...rowsData);
resolve();
}, 0);
});
};
const downloadDataExcel = async () => {
return new Promise(async (resolve, reject) => {
try {
if (!data || data.length === 0) {
reject(new Error('다운로드할 데이터가 없습니다.'));
return;
}
updateLoadingState(5);
// 데이터 플랫 변환 과정을 더 작은 청크로 나누기
const dataChunkSize = 2000; // 한 번에 처리할 데이터 아이템 수
const dataChunks = chunkArray(data, dataChunkSize);
let flattenedData = [];
for (let i = 0; i < dataChunks.length; i++) {
await new Promise(resolve => {
setTimeout(() => {
// 청크 내 아이템들을 플랫하게 변환
const chunkData = dataChunks[i].map(item => {
// 기본 필드
const baseData = {
'logTime': item.logTime,
'GUID': item.userGuid === 'None' ? '' : item.userGuid,
'Nickname': item.userNickname === 'None' ? '' : item.userNickname,
'Account ID': item.accountId === 'None' ? '' : item.accountId,
'Action': item.action,
'Domain': item.domain === 'None' ? '' : item.domain,
'Tran ID': item.tranId
};
// Actor 데이터 플랫하게 추가
const actorData = item.header && item.header.Actor ?
flattenObject(item.header.Actor, 'Actor') : {};
// Infos 데이터 플랫하게 추가
let infosData = {};
if (item.body && item.body.Infos && Array.isArray(item.body.Infos)) {
item.body.Infos.forEach((info) => {
infosData = {
...infosData,
...flattenObject(info, `Info`)
};
});
}
return {
...baseData,
...actorData,
...infosData
};
});
flattenedData = [...flattenedData, ...chunkData];
const progress = 5 + Math.floor((i + 1) / dataChunks.length * 10);
updateLoadingState(progress);
resolve();
}, 0);
});
}
// 모든 항목의 모든 키 수집하여 헤더 생성
const allKeys = new Set();
// 헤더 수집도 청크로 나누기
for (let i = 0; i < flattenedData.length; i += dataChunkSize) {
await new Promise(resolve => {
setTimeout(() => {
const end = Math.min(i + dataChunkSize, flattenedData.length);
for (let j = i; j < end; j++) {
Object.keys(flattenedData[j]).forEach(key => allKeys.add(key));
}
const progress = 15 + Math.floor((i + dataChunkSize) / flattenedData.length * 5);
updateLoadingState(progress);
resolve();
}, 0);
});
}
const headers = Array.from(allKeys);
updateLoadingState(20);
// 워크북 생성
const wb = XLSX.utils.book_new();
// 헤더 행 생성
const headerRow = headers.map(h => ({
v: h,
s: headerStyle
}));
// 청크로 데이터 나누기
const chunkSize = 500; // 한 번에 처리할 행의 수 (더 작게 조정)
const rowChunks = chunkArray(flattenedData, chunkSize);
const allDataRows = [];
// 각 청크 처리
let processedCount = 0;
for (const chunk of rowChunks) {
await processDataChunk(chunk, headers, allDataRows, processedCount, flattenedData.length);
processedCount += chunk.length;
// 메모리 정리를 위한 가비지 컬렉션 힌트
if (processedCount % (chunkSize * 10) === 0) {
// 5000행마다 짧은 지연을 두어 가비지 컬렉션 기회 제공
await new Promise(resolve => setTimeout(resolve, 10));
}
}
updateLoadingState(95);
// 워크시트 데이터 구성 및 파일 다운로드를 메인 로직과 분리
setTimeout(() => {
try {
// 워크시트 데이터 구성
const wsData = [
// 헤더 행
headerRow,
// 데이터 행들
...allDataRows
];
// 워크시트 생성 (메모리 최적화)
const ws = XLSX.utils.aoa_to_sheet(wsData);
// 열 너비 설정 (성능 최적화를 위해 샘플링)
ws['!cols'] = headers.map((header) => {
// 헤더 길이와 샘플 데이터 길이를 기준으로 열 너비 결정
const sampleSize = Math.min(flattenedData.length, 500);
const samples = [];
for (let i = 0; i < sampleSize; i++) {
const randomIndex = Math.floor(Math.random() * flattenedData.length);
const item = flattenedData[randomIndex];
if (item[header] !== undefined) {
samples.push(String(item[header]).length * 1.2);
}
}
const maxLength = Math.max(
header.length * 1.5,
...samples
);
return { wch: Math.max(10, Math.min(50, maxLength)) };
});
// 최종 단계 분리
setTimeout(() => {
try {
// 워크시트를 워크북에 추가
XLSX.utils.book_append_sheet(wb, ws, sheetName);
// 엑셀 파일 다운로드
XLSX.writeFile(wb, fileName);
updateLoadingState(100);
resolve();
} catch (error) {
reject(error);
}
}, 100);
} catch (error) {
reject(error);
}
}, 0);
} catch (error) {
reject(error);
}
});
};
const handleDownload = useCallback(async () => {
if (isDownloading) return; // 이미 다운로드 중이면 중복 실행 방지
setIsDownloading(true);
setLastProgress(0);
if (onLoadingChange) onLoadingChange({loading: true, progress: 0});
try {
if (tableRef) {
await downloadTableExcel();
} else if (data) {
await downloadDataExcel();
} else {
alert('유효한 데이터 소스가 없습니다.');
}
} catch (error) {
console.error('Excel download failed:', error);
alert('엑셀 다운로드 중 오류가 발생했습니다.');
} finally {
// 다운로드 완료 후 짧은 지연 시간을 두어 100% 상태를 잠시 보여줌
setTimeout(() => {
setIsDownloading(false);
if (onLoadingChange) onLoadingChange({loading: false, progress: 100});
}, 500);
}
}, [tableRef, data, fileName, sheetName, isDownloading, onLoadingChange]);
return ( return (
<ExcelDownButton onClick={downloadExcel}> <ExcelDownButton onClick={handleDownload} disabled={isDownloading}>
엑셀 다운로드 {isDownloading ? '다운로드 중...' : '엑셀 다운로드'}
</ExcelDownButton> </ExcelDownButton>
); );
}; };

View File

@@ -0,0 +1,113 @@
import * as XLSX from 'xlsx-js-style';
import { ExcelDownButton } from '../../../styles/ModuleComponents';
const ExcelDownloadButton = ({ tableRef, fileName = 'download.xlsx', sheetName = 'Sheet1' }) => {
const isNumeric = (value) => {
// 숫자 또는 숫자 문자열인지 확인
return !isNaN(value) && !isNaN(parseFloat(value));
};
const downloadExcel = () => {
try {
if (!tableRef.current) return;
const tableElement = tableRef.current;
const headerRows = tableElement.getElementsByTagName('thead')[0].getElementsByTagName('tr');
const bodyRows = tableElement.getElementsByTagName('tbody')[0].getElementsByTagName('tr');
// 헤더 데이터 추출
const headers = Array.from(headerRows[0].cells).map(cell => cell.textContent);
// 바디 데이터 추출 및 숫자 타입 처리
const bodyData = Array.from(bodyRows).map(row =>
Array.from(row.cells).map(cell => {
const value = cell.textContent;
return isNumeric(value) ? parseFloat(value) : value;
})
);
// 워크북 생성
const wb = XLSX.utils.book_new();
// 테두리 스타일 정의
const borderStyle = {
style: "thin",
color: { rgb: "000000" }
};
// 스타일 정의
const centerStyle = {
font: {
name: "맑은 고딕",
sz: 11
},
alignment: {
horizontal: 'right',
vertical: 'right'
},
border: {
top: borderStyle,
bottom: borderStyle,
left: borderStyle,
right: borderStyle
}
};
const headerStyle = {
alignment: {
horizontal: 'center',
vertical: 'center'
},
fill: {
fgColor: { rgb: "d9e1f2" },
patternType: "solid"
}
};
// 데이터에 스타일 적용
const wsData = [
// 헤더 행
headers.map(h => ({
v: h,
s: headerStyle
})),
// 데이터 행들
...bodyData.map(row =>
row.map(cell => ({
v: cell,
s: centerStyle
}))
)
];
// 워크시트 생성
const ws = XLSX.utils.aoa_to_sheet(wsData);
// 열 너비 설정 (최소 8, 최대 50)
ws['!cols'] = headers.map((_, index) => {
const maxLength = Math.max(
headers[index].length * 2,
...bodyData.map(row => String(row[index] || '').length * 1.2)
);
return { wch: Math.max(8, Math.min(50, maxLength)) };
});
// 워크시트를 워크북에 추가
XLSX.utils.book_append_sheet(wb, ws, sheetName);
// 엑셀 파일 다운로드
XLSX.writeFile(wb, fileName);
} catch (error) {
console.error('Excel download failed:', error);
alert('엑셀 다운로드 중 오류가 발생했습니다.');
}
};
return (
<ExcelDownButton onClick={downloadExcel}>
엑셀 다운로드
</ExcelDownButton>
);
};
export default ExcelDownloadButton;

View File

@@ -0,0 +1,62 @@
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
const Button = styled.button`
position: fixed;
bottom: 30px;
right: 30px;
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #666666;
color: white;
font-size: 16px;
display: ${props => (props.$show ? 'flex' : 'none')};
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.2);
z-index: 999;
&:hover {
background-color: #333333;
}
`;
const TopButton = () => {
const [visible, setVisible] = useState(false);
useEffect(() => {
const toggleVisibility = () => {
if (window.pageYOffset > 300) {
setVisible(true);
} else {
setVisible(false);
}
};
window.addEventListener('scroll', toggleVisibility);
return () => window.removeEventListener('scroll', toggleVisibility);
}, []);
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
return (
<Button
$show={visible}
onClick={scrollToTop}
title="맨 위로 이동"
>
</Button>
);
};
export default TopButton;

View File

@@ -11,9 +11,13 @@ import DynamicModal from './modal/DynamicModal';
import CustomConfirmModal from './modal/CustomConfirmModal'; import CustomConfirmModal from './modal/CustomConfirmModal';
import Modal from './modal/Modal'; import Modal from './modal/Modal';
import Pagination from './Pagination/Pagination'; import Pagination from './Pagination/Pagination';
import DynamoPagination from './Pagination/DynamoPagination';
import FrontPagination from './Pagination/FrontPagination';
import ViewTableInfo from './Table/ViewTableInfo'; import ViewTableInfo from './Table/ViewTableInfo';
import Loading from './Loading'; import Loading from './Loading';
import DownloadProgress from './DownloadProgress';
import CDivider from './CDivider'; import CDivider from './CDivider';
import TopButton from './button/TopButton';
export { export {
DatePickerComponent, DatePickerComponent,
DateTimeRangePicker, DateTimeRangePicker,
@@ -38,5 +42,9 @@ export { DateTimeInput,
Pagination, Pagination,
ViewTableInfo, ViewTableInfo,
Loading, Loading,
CDivider CDivider,
TopButton,
DynamoPagination,
FrontPagination,
DownloadProgress
}; };

View File

@@ -1,2 +0,0 @@
export {ivenTabType, modalTypes} from './types'
export {mailSendType, mailType, mailSendStatus, mailReceiveType, adminLevelType} from './options'

View File

@@ -1,34 +0,0 @@
export const mailSendType = [
{ value: 'ALL', name: '전체' },
{ value: 'RESERVE_SEND', name: '예약 발송' },
{ value: 'DIRECT_SEND', name: '즉시 발송' },
];
export const mailSendStatus = [
{ value: 'ALL', name: '전체' },
{ value: 'WAIT', name: '대기' },
{ value: 'FINISH', name: '완료' },
{ value: 'FAIL', name: '실패' },
{ value: 'RUNNING', name: '전송중' },
];
export const mailType = [
{ value: 'ALL', name: '전체' },
{ value: 'SYSTEM_GUID', name: '시스템 안내' },
{ value: 'INSPECTION_COMPENSATION', name: '점검 보상' },
{ value: 'RECOVER_COMPENSATION', name: '복구 보상' },
{ value: 'EVENT_COMPENSATION', name: '이벤트 보상' },
];
export const mailReceiveType = [
{ value: 'ALL', name: '전체' },
{ value: 'SINGLE', name: '단일' },
{ value: 'MULTIPLE', name: '복수' },
];
export const adminLevelType = [
{ value: '0', name: '없음' },
{ value: '1', name: 'GM' },
{ value: '2', name: 'Super GM' },
{ value: '3', name: 'Developer' },
]

View File

@@ -1,16 +0,0 @@
export const ivenTabType = {
CLOTH: "cloth",
PROP: "prop",
BEAUTY: "beauty",
TATTOO: "tattoo",
CURRENCY: "currency",
ETC: "etc"
};
export const modalTypes = {
confirmOkCancel: "confirmOkCancel",
completed: "completed",
childOkCancel: "childOkCancel",
}

View File

@@ -1,9 +1,10 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { AuthModal } from '../components/common'; import { AuthModal } from '../components/common';
import { authList } from '../store/authList'; import { authList } from '../store/authList';
import { INITIAL_CURRENT_PAGE, INITIAL_PAGE_SIZE } from '../assets/data/adminConstants'; import { INITIAL_CURRENT_PAGE, INITIAL_PAGE_SIZE } from '../assets/data/adminConstants';
import { PageSkeleton } from '../components/Skeleton/PageSkeleton'; import { PageSkeleton } from '../components/Skeleton/PageSkeleton';
import { adminAuthLevel, authType } from '../assets/data/types';
export const useDateTimeState = (initialDate = '') => { export const useDateTimeState = (initialDate = '') => {
const [date, setDate] = useState(initialDate); const [date, setDate] = useState(initialDate);
@@ -67,10 +68,22 @@ export const withAuth = (requiredAuth) => (WrappedComponent) => {
); );
} }
const hasRequiredAuth = userInfo.auth_list && const authLevelPermissions = {
userInfo.auth_list.some(auth => auth.id === requiredAuth); [adminAuthLevel.DEVELOPER]: [authType.levelReader, authType.levelMaster, authType.levelDeveloper],
[adminAuthLevel.MASTER]: [authType.levelReader, authType.levelMaster],
[adminAuthLevel.READER]: [authType.levelReader]
};
if (!hasRequiredAuth) { const allowedAuthTypes = authLevelPermissions[userInfo.auth_level_type] || [];
const adminAuth = allowedAuthTypes.includes(requiredAuth);
if (adminAuth) {
return <WrappedComponent {...props} />;
}
const hasRequiredAuth = userInfo.auth_list.some(auth => auth.id === requiredAuth);
if (!hasRequiredAuth && !adminAuth) {
return <AuthModal />; return <AuthModal />;
} }
@@ -81,10 +94,20 @@ export const withAuth = (requiredAuth) => (WrappedComponent) => {
export const useTable = (tableData = [], options = {mode: 'multi'}) => { export const useTable = (tableData = [], options = {mode: 'multi'}) => {
const [selectedRows, setSelectedRows] = useState([]); const [selectedRows, setSelectedRows] = useState([]);
const tableDataRef = useRef(tableData);
// tableData가 변경될 때 선택된 행 초기화 // tableData가 변경될 때 선택된 행 초기화
useEffect(() => { useEffect(() => {
setSelectedRows([]); const hasDataChanged =
tableData.length !== tableDataRef.current.length ||
tableData.some((item, index) =>
tableDataRef.current[index]?.id !== item.id
);
if (hasDataChanged) {
setSelectedRows([]);
tableDataRef.current = tableData;
}
}, [tableData]); }, [tableData]);
// 단일 행 선택/해제 // 단일 행 선택/해제
@@ -166,3 +189,76 @@ export const useDataFetch = (fetchFunction, dependencies = [], initialState = nu
setData setData
}; };
}; };
export const useDynamoDBPagination = (fetchFunction) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({
currentPage: 1,
pageKeys: { 1: null },
hasNextPage: false
});
const resetPagination = useCallback(() => {
setPagination({
currentPage: 1,
pageKeys: { 1: null },
hasNextPage: false
});
}, []);
const fetchPage = useCallback(async (page) => {
setLoading(true);
try {
const startKey = pagination.pageKeys[page];
const response = await fetchFunction(page, startKey);
setData(response);
const updatedPagination = { ...pagination, currentPage: page };
if (response.pageKey) {
updatedPagination.pageKeys[page + 1] = response.pageKey;
updatedPagination.hasNextPage = true;
} else {
updatedPagination.hasNextPage = false;
}
setPagination(updatedPagination);
return response;
} catch (error) {
console.error('페이지 데이터 가져오기 오류:', error);
throw error;
} finally {
setLoading(false);
}
}, [fetchFunction, pagination]);
const goToNextPage = useCallback(() => {
if (!pagination.hasNextPage) return;
const nextPage = pagination.currentPage + 1;
return fetchPage(nextPage);
}, [pagination, fetchPage]);
const goToPrevPage = useCallback(() => {
if (pagination.currentPage <= 1) return;
const prevPage = pagination.currentPage - 1;
return fetchPage(prevPage);
}, [pagination, fetchPage]);
return {
data,
loading,
pagination,
setData,
setPagination,
fetchPage,
goToNextPage,
goToPrevPage,
resetPagination
};
};

View File

@@ -1 +0,0 @@
// 공통으로 사용될 함수를 관리하는 폴더입니다. 이 파일은 삭제하셔도 됩니다.

View File

@@ -8,6 +8,7 @@ const resources = {
DATE_KTC: '* UTC+9 한국시간 기준으로 설정 (UTC+0 자동 반영처리)', DATE_KTC: '* UTC+9 한국시간 기준으로 설정 (UTC+0 자동 반영처리)',
NOT_ITEM: '존재하지 않는 아이템코드입니다.', NOT_ITEM: '존재하지 않는 아이템코드입니다.',
REGIST_COMPLTE: "등록이 완료되었습니다.", REGIST_COMPLTE: "등록이 완료되었습니다.",
INIT_COMPLTE: "초기화 등록이 완료되었습니다.\n순차적으로 처리됩니다.",
REGIST_FAIL: '등록에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.', REGIST_FAIL: '등록에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.',
UPDATE_FAIL: '수정에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.', UPDATE_FAIL: '수정에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.',
STOP_FAIL: '중단에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.', STOP_FAIL: '중단에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.',
@@ -15,6 +16,9 @@ const resources = {
API_FAIL: '처리 중 오류가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요. 오류가 지속될 경우, 담당자에게 문의해주세요.', API_FAIL: '처리 중 오류가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요. 오류가 지속될 경우, 담당자에게 문의해주세요.',
USER_MAIL_DEL_CONFIRM: '해당 우편을 삭제하시겠습니까?', USER_MAIL_DEL_CONFIRM: '해당 우편을 삭제하시겠습니까?',
USER_GM_CHANGE: 'GM 권한을 변경하시겠습니까?', USER_GM_CHANGE: 'GM 권한을 변경하시겠습니까?',
USER_GM_CHANGE_COMPLETE: '권한변경을 완료하였습니다.',
USER_KICK_CONFIRM: '해당 유저를 Kick 하시겠습니까?',
USER_KICK_COMPLETE: '서버에 정상적으로 Kick을 요청하였습니다.',
DEL_COUNT_CHECK: '보유 개수 이상 삭제할 수 없습니다.', DEL_COUNT_CHECK: '보유 개수 이상 삭제할 수 없습니다.',
DEL_COUNT_CONFIRM: '삭제할 아이템 개수를 입력하세요.\n(보유 개수: {{count}})', DEL_COUNT_CONFIRM: '삭제할 아이템 개수를 입력하세요.\n(보유 개수: {{count}})',
CANCEL_CONFIRM: '취소하시겠습니까?\n취소 시 변경된 값은 초기화됩니다.', CANCEL_CONFIRM: '취소하시겠습니까?\n취소 시 변경된 값은 초기화됩니다.',
@@ -37,7 +41,18 @@ const resources = {
WARNING_NICKNAME_CHECK: '닉네임을 확인해주세요.', WARNING_NICKNAME_CHECK: '닉네임을 확인해주세요.',
WARNING_EMAIL_CHECK: '이메일을 확인해주세요.', WARNING_EMAIL_CHECK: '이메일을 확인해주세요.',
WARNING_TYPE_CHECK: '타입을 확인해주세요.', WARNING_TYPE_CHECK: '타입을 확인해주세요.',
//db
LOG_MEMORY_LIMIT_WARNING: '데이터가 너무 많아 조회할 수 없습니다.\n조회조건 조정 후 다시 조회해주세요.',
LOG_MONGGDB_QUERY_WARNING: '조회 중 오류가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요.\n오류가 지속될 경우, 담당자에게 문의해주세요.',
DATA_INIT_CONFIRM: '대상 {{data}}\n데이터를 초기화 하시겠습니까?\n초기화시 되돌릴 수 없습니다.',
//랜드 //랜드
LAND_OWNED_CHANGES_WARNING: "해당 랜드는 소유권 변경이 불가능합니다.",
LAND_OWNED_CHANGES_REGIST_CONFIRM: "랜드 소유권 변경을 등록하시겠습니까?",
LAND_OWNED_CHANGES_SELECT_DELETE: "랜드 소유권 변경 예약을 취소하시겠습니까?",
LAND_OWNED_CHANGES_DELETE_TIME_WARNING: "예약시간이 지나 취소할 수 없습니다.",
LAND_OWNED_CHANGES_DELETE_STATUS_WARNING: "소유권 변경 예약을 취소할 수 없는 상태입니다.",
LAND_OWNED_CHANGES_REGIST_DUPLICATION_WARNING: "소유권 변경이 진행중인 랜드입니다.",
LAND_OWNER_DUPLICATION_WARNING: "소유자가 존재하는 랜드입니다.",
LAND_AUCTION_SELECT_DELETE: "선택된 경매를 삭제하시겠습니까?", LAND_AUCTION_SELECT_DELETE: "선택된 경매를 삭제하시겠습니까?",
LAND_AUCTION_WARNING_DELETE: "대기 상태의 경매만 삭제할 수 있습니다.", LAND_AUCTION_WARNING_DELETE: "대기 상태의 경매만 삭제할 수 있습니다.",
LAND_AUCTION_MODAL_STATUS_WARNING: "경매 시작일시 이후에는 변경이 불가합니다.", LAND_AUCTION_MODAL_STATUS_WARNING: "경매 시작일시 이후에는 변경이 불가합니다.",
@@ -88,8 +103,8 @@ const resources = {
BATTLE_EVENT_MODAL_START_DT_WARNING: "시작 시간은 현재 시간으로부터 10분 이후부터 가능합니다.", BATTLE_EVENT_MODAL_START_DT_WARNING: "시작 시간은 현재 시간으로부터 10분 이후부터 가능합니다.",
BATTLE_EVENT_MODAL_START_DIFF_END_WARNING :"종료일은 시작일보다 하루 이후여야 합니다.", BATTLE_EVENT_MODAL_START_DIFF_END_WARNING :"종료일은 시작일보다 하루 이후여야 합니다.",
BATTLE_EVENT_MODAL_TIME_CHECK_WARNING :"해당 시간에 속하는 이벤트가 존재합니다.", BATTLE_EVENT_MODAL_TIME_CHECK_WARNING :"해당 시간에 속하는 이벤트가 존재합니다.",
BATTLE_EVENT_REGIST_CONFIRM: "랜드 경매를 등록하시겠습니까?", BATTLE_EVENT_REGIST_CONFIRM: "이벤트를 등록하시겠습니까?",
BATTLE_EVENT_UPDATE_CONFIRM: "랜드 경매를 수정하시겠습니까?", BATTLE_EVENT_UPDATE_CONFIRM: "이벤트를 수정하시겠습니까?",
BATTLE_EVENT_SELECT_DELETE: "선택된 이벤트를 삭제하시겠습니까?", BATTLE_EVENT_SELECT_DELETE: "선택된 이벤트를 삭제하시겠습니까?",
BATTLE_EVENT_SELECT_STOP: "선택된 이벤트를 중단하시겠습니까?", BATTLE_EVENT_SELECT_STOP: "선택된 이벤트를 중단하시겠습니까?",
BATTLE_EVENT_STOP_5MINUTES_DATE_WARNING: "이벤트 시작 5분 전에는 중단할 수 없습니다.", BATTLE_EVENT_STOP_5MINUTES_DATE_WARNING: "이벤트 시작 5분 전에는 중단할 수 없습니다.",
@@ -99,6 +114,7 @@ const resources = {
FILE_INDEX_USER_CONTENT: 'Caliverse_User_Index.xlsx', FILE_INDEX_USER_CONTENT: 'Caliverse_User_Index.xlsx',
FILE_CALIUM_REQUEST: 'Caliverse_Calium_Request.xlsx', FILE_CALIUM_REQUEST: 'Caliverse_Calium_Request.xlsx',
FILE_LAND_AUCTION: 'Caliverse_Land_Auction.xlsx', FILE_LAND_AUCTION: 'Caliverse_Land_Auction.xlsx',
FILE_BUSINESS_LOG: 'Caliverse_Log.xlsx',
FILE_BATTLE_EVENT: 'Caliverse_Battle_Event.xlsx' FILE_BATTLE_EVENT: 'Caliverse_Battle_Event.xlsx'
} }
}, },

View File

@@ -0,0 +1,292 @@
import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
Title,
TableStyle,
FormWrapper,
TableWrapper,
TableActionButton,
TableDetailRow,
TableDetailContainer,
TableDetailFlex,
TableDetailColumn,
DetailTableInfo,
} from '../../styles/Components';
import { withAuth } from '../../hooks/hook';
import {
authType,
modalTypes,
} from '../../assets/data';
import { INITIAL_PAGE_LIMIT, INITIAL_PAGE_SIZE } from '../../assets/data/adminConstants';
import { useTranslation } from 'react-i18next';
import {
DownloadProgress,
DynamicModal,
ExcelDownButton,
Pagination,
TopButton,
ViewTableInfo,
} from '../../components/common';
import { TableSkeleton } from '../../components/Skeleton/TableSkeleton';
import BusinessLogSearchBar, { useBusinessLogSearch } from '../../components/ServiceManage/searchBar/BusinessLogSearchBar';
import styled from 'styled-components';
import FrontPagination from '../../components/common/Pagination/FrontPagination';
import Loading from '../../components/common/Loading';
import CircularProgress from '../../components/common/CircularProgress';
const BusinessLogView = () => {
const token = sessionStorage.getItem('token');
const { t } = useTranslation();
const tableRef = useRef(null);
const [alertMsg, setAlertMsg] = useState('');
const [expandedRows, setExpandedRows] = useState({});
const [downloadState, setDownloadState] = useState({
loading: false,
progress: 0
});
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(500);
const [displayData, setDisplayData] = useState([]);
const {
searchParams,
loading: dataLoading,
data: dataList,
handleSearch,
handleReset,
handlePageChange,
handleOrderByChange,
updateSearchParams
} = useBusinessLogSearch(token, 500, setAlertMsg);
const handlePageSizeChange = (newSize) => {
setItemsPerPage(newSize);
setCurrentPage(1);
};
const handleClientPageChange = useCallback((slicedData) => {
setDisplayData(slicedData);
}, []);
useEffect(() => {
setCurrentPage(1);
if (dataList?.generic_list && dataList.generic_list.length > 0) {
const initialData = dataList.generic_list.slice(0, itemsPerPage);
setDisplayData(initialData);
} else {
setDisplayData([]);
}
}, [dataList, itemsPerPage]);
const toggleRowExpand = (index) => {
setExpandedRows(prev => ({
...prev,
[index]: !prev[index]
}));
};
const tableHeaders = useMemo(() => {
return [
{ id: 'logTime', label: '일시', width: '120px' },
{ id: 'userGuid', label: 'GUID', width: '200px' },
{ id: 'accountId', label: 'account ID', width: '100px' },
{ id: 'userNickname', label: '아바타명', width: '150px' },
{ id: 'tranId', label: '트랜잭션 ID', width: '200px' },
{ id: 'action', label: '액션', width: '150px' },
{ id: 'domain', label: '도메인', width: '120px' },
{ id: 'details', label: '상세 정보', width: '100px' }
];
}, []);
const renderActorData = (actor) => {
if (!actor || typeof actor !== 'object') return <></>;
return (
<DetailTableInfo>
<thead>
<tr>
<th colSpan="2">Actor 정보</th>
</tr>
</thead>
<tbody>
{Object.entries(actor).map(([key, value]) => (
<tr key={`actor-${key}`}>
<td>{key}</td>
<td>
{typeof value === 'object' && value !== null
? JSON.stringify(value)
: String(value)
}
</td>
</tr>
))}
</tbody>
</DetailTableInfo>
);
};
const renderInfosData = (infos) => {
if (!infos || !Array.isArray(infos) || infos.length === 0) return <></>;
return (
<DetailTableInfo>
<thead>
<tr>
<th colSpan="2">Infos 정보</th>
</tr>
</thead>
<tbody>
{infos.map((info, infoIndex) => (
<Fragment key={`info-${infoIndex}`}>
{Object.entries(info).map(([key, value]) => (
<tr key={`info-${infoIndex}-${key}`}>
<td>{key}</td>
<td>
{typeof value === 'object' && value !== null
? JSON.stringify(value)
: Array.isArray(value)
? value.join(', ')
: String(value)
}
</td>
</tr>
))}
</Fragment>
))}
</tbody>
</DetailTableInfo>
);
};
const handleModalSubmit = async (type, param = null) => {
switch (type) {
case "warning":
setAlertMsg('')
break;
}
}
return (
<>
<Title>비즈니스 로그 조회</Title>
<FormWrapper>
<BusinessLogSearchBar
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} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
<DownloadContainer>
<ExcelDownButton
data={dataList?.generic_list}
fileName={t('FILE_BUSINESS_LOG')}
onLoadingChange={setDownloadState}
/>
{downloadState.loading && (
<CircularProgressWrapper>
<CircularProgress
progress={downloadState.progress}
size={36}
progressColor="#4A90E2"
/>
</CircularProgressWrapper>
)}
</DownloadContainer>
</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>
{displayData?.map((item, index) => (
<Fragment key={index}>
<tr>
<td>{item.logTime}</td>
<td>{item.userGuid}</td>
<td>{item.accountId}</td>
<td>{item.userNickname}</td>
<td>{item.tranId}</td>
<td>{item.action}</td>
<td>{item.domain === 'None' ? '-' : item.domain}</td>
<td>
<TableActionButton onClick={() => toggleRowExpand(index)}>
{expandedRows[index] ? '접기' : '상세보기'}
</TableActionButton>
</td>
</tr>
{expandedRows[index] && (
<TableDetailRow>
<td colSpan={tableHeaders.length}>
<TableDetailContainer>
<TableDetailFlex>
<TableDetailColumn>
{renderActorData(item.header?.Actor)}
</TableDetailColumn>
<TableDetailColumn>
{renderInfosData(item.body?.Infos)}
</TableDetailColumn>
</TableDetailFlex>
</TableDetailContainer>
</td>
</TableDetailRow>
)}
</Fragment>
))}
</tbody>
</TableStyle>
</TableWrapper>
{dataList?.generic_list &&
<FrontPagination
data={dataList.generic_list}
itemsPerPage={itemsPerPage}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
pageLimit={10}
onPageChange={handleClientPageChange}
/>}
<TopButton />
</>
}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleModalSubmit('warning')}
/>
</>
);
};
export default withAuth(authType.businessLogRead)(BusinessLogView);
const DownloadContainer = styled.div`
display: flex;
align-items: center;
gap: 10px;
`;
const CircularProgressWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
`;

View File

@@ -0,0 +1,285 @@
import { styled } from 'styled-components';
import { Link } from 'react-router-dom';
import React, { Fragment, useRef, useState } from 'react';
import {
Title,
TableStyle,
FormWrapper,
TableWrapper,
} from '../../styles/Components';
import Button from '../../components/common/button/Button';
import { useNavigate } from 'react-router-dom';
import { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil';
import { useDataFetch, useModal, useTable, withAuth } from '../../hooks/hook';
import {
authType,
landSize,
modalTypes,
opLandCategoryType,
opLandOwnedType,
} from '../../assets/data';
import { INITIAL_PAGE_LIMIT, INITIAL_PAGE_SIZE, TYPE_MODIFY } from '../../assets/data/adminConstants';
import { useTranslation } from 'react-i18next';
import { CheckBox, DynamicModal, ExcelDownButton, Pagination, ViewTableInfo } from '../../components/common';
import LandInfoSearchBar, { useLandInfoSearch } from '../../components/ServiceManage/searchBar/LandInfoSearchBar';
import { TableSkeleton } from '../../components/Skeleton/TableSkeleton';
import OwnerChangeModal from '../../components/ServiceManage/modal/OwnerChangeModal';
import { opLandInfoStatusType } from '../../assets/data/options';
const LandInfoView = () => {
const token = sessionStorage.getItem('token');
const navigate = useNavigate();
const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const tableRef = useRef(null);
const [detailData, setDetailData] = useState({});
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
detail: 'hidden',
deleteConfirm: 'hidden',
deleteComplete: 'hidden'
});
const [alertMsg, setAlertMsg] = useState('');
const [modalType, setModalType] = useState('regist');
const {
searchParams,
loading,
data: dataList,
handleSearch,
handleReset,
handlePageChange,
handlePageSizeChange,
handleOrderByChange,
updateSearchParams
} = useLandInfoSearch(token, INITIAL_PAGE_SIZE);
const {
selectedRows,
handleSelectRow,
isRowSelected,
removeSelectedRows
} = useTable(dataList?.land_info_list || [], {mode: 'single'});
// const {
// data: landData
// } = useDataFetch(() => LandView(token), [token]);
const handleModalSubmit = async (type, param = null) => {
switch (type) {
case "regist":
setModalType('regist');
const selectRow = selectedRows[0];
if(!selectRow.owned) {
setAlertMsg(t('LAND_OWNED_CHANGES_WARNING'))
return;
}
const owner_changes = selectRow.owner_changes;
if(owner_changes.length > 0){
setModalType(TYPE_MODIFY);
}
setDetailData(selectRow);
handleModalView('detail');
break;
// case "detail":
// await LandAuctionDetailView(token, param).then(data => {
// setDetailData(data.auction_detail);
// setModalType('modify');
// handleModalView('detail');
// });
// break;
// case "delete":
// const date_check = selectedRows.every(row => {
// const timeDiff = timeDiffMinute(convertKTC(row.auction_start_dt), (new Date));
// return timeDiff < 3;
// });
// if(date_check){
// setAlertMsg(t('LAND_AUCTION_DELETE_DATE_WARNING'));
// return;
// }
// if(selectedRows[0].status === landAuctionStatusType.auction_start || selectedRows[0].status === landAuctionStatusType.stl_end){
// setAlertMsg(t('LAND_AUCTION_DELETE_STATUS_WARNING'));
// return;
// }
// handleModalView('deleteConfirm');
// break;
// case "deleteConfirm":
// let list = [];
// let isChecked = false;
//
// selectedRows.map(data => {
// // const row = dataList.list.find(row => row.id === Number(data.id));
// // if(row.status !== commonStatus.wait) isChecked = true;
// list.push({
// id: data.id,
// });
// });
//
// if(isChecked) {
// setAlertMsg(t('LAND_AUCTION_WARNING_DELETE'))
// handleModalClose('deleteConfirm');
// return;
// }
//
// await LandAuctionDelete(token, list).then(data => {
// handleModalClose('deleteConfirm');
// if(data.result === "SUCCESS") {
// handleModalView('deleteComplete');
// }else{
// setAlertMsg(t('DELETE_FAIL'));
// }
// }).catch(reason => {
// setAlertMsg(t('API_FAIL'));
// });
//
// break;
// case "deleteComplete":
// handleModalClose('deleteComplete');
// // fetchData(option);
// window.location.reload();
// break;
case "warning":
setAlertMsg('')
break;
}
}
const handleDetailView = () => {
handleModalClose('detail');
handleSearch();
removeSelectedRows();
}
return (
<>
<Title>랜드 정보 조회</Title>
<FormWrapper>
<LandInfoSearchBar
searchParams={searchParams}
onSearch={(newParams, executeSearch = true) => {
if (executeSearch) {
handleSearch(newParams);
} else {
updateSearchParams(newParams);
}
}}
onReset={handleReset}
/>
</FormWrapper>
<ViewTableInfo total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
<ExcelDownButton tableRef={tableRef} fileName={t('FILE_LAND_AUCTION')} />
{userInfo.auth_list?.some(auth => auth.id === authType.landUpdate) && (
<Button
theme={selectedRows.length === 0 ? 'disable' : 'line'}
text="소유권 변경"
type="button"
handleClick={e => handleModalSubmit('regist')}
/>
)}
</ViewTableInfo>
{loading ? <TableSkeleton width='100%' count={15} /> :
<>
<TableWrapper>
<TableStyle ref={tableRef}>
<thead>
<tr>
<th width="40"></th>
<th width="150">랜드 ID</th>
<th>랜드 이름</th>
<th>랜드 상태</th>
<th>카테고리</th>
<th>랜드 크기</th>
<th>인스턴스 </th>
<th>유저 소유 여부</th>
<th>보유자</th>
<th>보유시작일</th>
<th>낙찰 가격</th>
</tr>
</thead>
<tbody>
{dataList?.land_info_list?.map((data, index) => (
<Fragment key={index}>
<tr>
<td>
<CheckBox name={'select'} id={data.id}
setData={(e) => handleSelectRow(e, data)}
checked={isRowSelected(data.id)} />
</td>
<td>{data.land_id}</td>
<td>{data.land_name}</td>
<td>{opLandInfoStatusType.find(option => option.value === data.status)?.name}</td>
<td>{opLandCategoryType.find(option => option.value === data.category)?.name}</td>
<td>{landSize.find(option => option.value === data.land_size)?.name}</td>
<td>{data.socket}</td>
<td>{opLandOwnedType.find(option => option.value === data.owned)?.name}</td>
<td>{data.owner_user_nickname}</td>
{/*<td>{convertKTCDate(data.owner_user_create_date)}</td>*/}
<td>{data.owner_user_create_date}</td>
<td>{Number(data.owner_price) > 0 ? data.owner_price : ''}</td>
</tr>
</Fragment>
))}
</tbody>
</TableStyle>
</TableWrapper>
<Pagination postsPerPage={searchParams.pageSize} totalPosts={dataList?.total_all} setCurrentPage={handlePageChange} currentPage={searchParams.currentPage} pageLimit={INITIAL_PAGE_LIMIT} />
</>
}
<OwnerChangeModal modalType={modalType} detailView={modalState.detailModal} handleDetailView={() => handleDetailView()} content={detailData} setDetailData={setDetailData} />
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleModalSubmit('warning')}
/>
</>
);
};
export default withAuth(authType.landRead)(LandInfoView);
// const TableWrapper = styled.div`
// overflow: auto;
// border-top: 1px solid #000;
// z-index: 1;
// &::-webkit-scrollbar {
// height: 4px;
// }
// &::-webkit-scrollbar-thumb {
// background: #666666;
// }
// &::-webkit-scrollbar-track {
// background: #d9d9d9;
// }
// thead {
// th {
// position: sticky;
// top: 0;
// z-index: 10;
// }
// }
// ${TableStyle} {
// min-width: 1000px;
// th {
// position: sticky;
// top: 0;
// }
// }
// `;
const LandLink = styled(Link)`
color: #61a2d0;
text-decoration: underline;
`;

View File

@@ -1,238 +0,0 @@
import { styled } from 'styled-components';
import { Link } from 'react-router-dom';
import { Fragment, useRef, useState } from 'react';
import { Title, TableStyle, BtnWrapper, ButtonClose, ModalText, FormWrapper } from '../../styles/Components';
import LandSearchBar from '../../components/DataManage/LandSearchBar';
import Button from '../../components/common/button/Button';
import QuestDetailModal from '../../components/DataManage/QuestDetailModal';
import LandDetailModal from '../../components/DataManage/LandDetailModal';
import Modal from '../../components/common/modal/Modal';
import { useNavigate } from 'react-router-dom';
import { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil';
import { useDataFetch, useModal, useTable, withAuth } from '../../utils/hook';
import { authType, landAuctionStatusType } from '../../assets/data';
import { useLandAuctionSearch } from '../../components/ServiceManage/searchBar/LandAuctionSearchBar';
import { INITIAL_PAGE_SIZE } from '../../assets/data/adminConstants';
import { LandAuctionDelete, LandAuctionDetailView } from '../../apis';
import { convertKTC, timeDiffMinute } from '../../utils';
import { useTranslation } from 'react-i18next';
import { ExcelDownButton, ViewTableInfo } from '../../components/common';
import { LandAuctionSearchBar } from '../../components/ServiceManage';
const LandView = () => {
const token = sessionStorage.getItem('token');
const navigate = useNavigate();
const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const tableRef = useRef(null);
const [detailData, setDetailData] = useState({});
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
detail: 'hidden',
deleteConfirm: 'hidden',
deleteComplete: 'hidden'
});
const [alertMsg, setAlertMsg] = useState('');
const [modalType, setModalType] = useState('regist');
const {
searchParams,
data: dataList,
handleSearch,
handleReset,
handlePageChange,
handlePageSizeChange,
handleOrderByChange,
updateSearchParams
} = useLandAuctionSearch(token, INITIAL_PAGE_SIZE);
const {
selectedRows,
handleSelectRow,
isRowSelected
} = useTable(dataList?.auction_list || [], {mode: 'single'});
const {
data: landData
} = useDataFetch(() => LandView(token), [token]);
const handleModalSubmit = async (type, param = null) => {
switch (type) {
// case "regist":
// setModalType('regist');
// handleModalView('detail');
// break;
// case "detail":
// await LandAuctionDetailView(token, param).then(data => {
// setDetailData(data.auction_detail);
// setModalType('modify');
// handleModalView('detail');
// });
// break;
// case "delete":
// const date_check = selectedRows.every(row => {
// const timeDiff = timeDiffMinute(convertKTC(row.auction_start_dt), (new Date));
// return timeDiff < 3;
// });
// if(date_check){
// setAlertMsg(t('LAND_AUCTION_DELETE_DATE_WARNING'));
// return;
// }
// if(selectedRows[0].status === landAuctionStatusType.auction_start || selectedRows[0].status === landAuctionStatusType.stl_end){
// setAlertMsg(t('LAND_AUCTION_DELETE_STATUS_WARNING'));
// return;
// }
// handleModalView('deleteConfirm');
// break;
// case "deleteConfirm":
// let list = [];
// let isChecked = false;
//
// selectedRows.map(data => {
// // const row = dataList.list.find(row => row.id === Number(data.id));
// // if(row.status !== commonStatus.wait) isChecked = true;
// list.push({
// id: data.id,
// });
// });
//
// if(isChecked) {
// setAlertMsg(t('LAND_AUCTION_WARNING_DELETE'))
// handleModalClose('deleteConfirm');
// return;
// }
//
// await LandAuctionDelete(token, list).then(data => {
// handleModalClose('deleteConfirm');
// if(data.result === "SUCCESS") {
// handleModalView('deleteComplete');
// }else{
// setAlertMsg(t('DELETE_FAIL'));
// }
// }).catch(reason => {
// setAlertMsg(t('API_FAIL'));
// });
//
// break;
// case "deleteComplete":
// handleModalClose('deleteComplete');
// // fetchData(option);
// window.location.reload();
// break;
// case "warning":
// setAlertMsg('')
// break;
}
}
return (
<>
<Title>랜드 조회</Title>
<FormWrapper>
<LandAuctionSearchBar
searchParams={searchParams}
onSearch={(newParams, executeSearch = true) => {
if (executeSearch) {
handleSearch(newParams);
} else {
updateSearchParams(newParams);
}
}}
onReset={handleReset}
/>
</FormWrapper>
<ViewTableInfo total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
<ExcelDownButton tableRef={tableRef} fileName={t('FILE_LAND_AUCTION')} />
{userInfo.auth_list?.some(auth => auth.id === authType.landDelete) && (
<Button theme={selectedRows.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={() => handleModalSubmit('delete')} />
)}
{userInfo.auth_list?.some(auth => auth.id === authType.landUpdate) && (
<Button
theme={selectedRows.length === 0 ? 'disable' : 'line'}
text="소유권 변경"
type="button"
handleClick={e => handleModalSubmit('regist')}
/>
)}
</ViewTableInfo>
<TableWrapper>
<TableStyle ref={tableRef}>
<thead>
<tr>
<th width="150">랜드 ID</th>
<th>랜드 이름</th>
<th>랜드 상태</th>
<th>카테고리</th>
<th>랜드 크기</th>
<th>보유시작일</th>
<th width="150">상세보기</th>
</tr>
</thead>
<tbody>
{dataList.map((data, index) => (
<Fragment key={index}>
<tr>
<td>{data.landId}</td>
<td>{data.ownerNick}</td>
<td>{data.ownerId}</td>
<td>{new Date(data.lockInDate).toLocaleString()}</td>
<td>
<LandLink to={data.landUrl}>{data.landUrl}</LandLink>
</td>
<td>
{/*<Button theme="line" text="상세보기" handleClick={handleClick} />*/}
</td>
</tr>
</Fragment>
))}
</tbody>
</TableStyle>
</TableWrapper>
{/*<LandDetailModal detailPop={detailPop} handleClick={handleClick} />*/}
</>
);
};
export default withAuth(authType.landRead)(LandView);
const TableWrapper = styled.div`
overflow: auto;
border-top: 1px solid #000;
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-thumb {
background: #666666;
}
&::-webkit-scrollbar-track {
background: #d9d9d9;
}
thead {
th {
position: sticky;
top: 0;
z-index: 10;
}
}
${TableStyle} {
min-width: 1000px;
th {
position: sticky;
top: 0;
}
}
`;
const LandLink = styled(Link)`
color: #61a2d0;
text-decoration: underline;
`;

View File

@@ -1,4 +1,5 @@
export { default as UserView } from './UserView'; export { default as UserView } from './UserView';
export { default as LandView } from './LandView'; export { default as LandInfoView } from './LandInfoView';
export { default as GameLogView } from './GameLogView'; export { default as GameLogView } from './GameLogView';
export { default as CryptView } from './CryptView'; export { default as CryptView } from './CryptView';
export { default as BusinessLogView} from './BusinessLogView';

View File

@@ -31,7 +31,7 @@ import {
import { convertKTC, convertKTCDate, convertUTC, timeDiffMinute } from '../../utils'; import { convertKTC, convertKTCDate, convertUTC, timeDiffMinute } from '../../utils';
import { BattleEventModal } from '../../components/ServiceManage'; import { BattleEventModal } from '../../components/ServiceManage';
import { INITIAL_PAGE_SIZE, INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants'; import { INITIAL_PAGE_SIZE, INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
import { useDataFetch, useModal, useTable, withAuth } from '../../utils/hook'; import { useDataFetch, useModal, useTable, withAuth } from '../../hooks/hook';
import { StatusWapper, StatusLabel } from '../../styles/ModuleComponents'; import { StatusWapper, StatusLabel } from '../../styles/ModuleComponents';
import { battleEventStatus, battleRepeatType } from '../../assets/data/options'; import { battleEventStatus, battleRepeatType } from '../../assets/data/options';
import BattleEventSearchBar, { import BattleEventSearchBar, {

View File

@@ -29,7 +29,7 @@ import {
import { convertKTC, timeDiffMinute } from '../../utils'; import { convertKTC, timeDiffMinute } from '../../utils';
import { LandAuctionModal, LandAuctionSearchBar } from '../../components/ServiceManage'; import { LandAuctionModal, LandAuctionSearchBar } from '../../components/ServiceManage';
import { INITIAL_PAGE_SIZE, INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants'; import { INITIAL_PAGE_SIZE, INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
import { useDataFetch, useModal, useTable, withAuth } from '../../utils/hook'; import { useDataFetch, useModal, useTable, withAuth } from '../../hooks/hook';
import { useLandAuctionSearch } from '../../components/ServiceManage/searchBar/LandAuctionSearchBar'; import { useLandAuctionSearch } from '../../components/ServiceManage/searchBar/LandAuctionSearchBar';
import { StatusWapper, ChargeBtn, StatusLabel } from '../../styles/ModuleComponents'; import { StatusWapper, ChargeBtn, StatusLabel } from '../../styles/ModuleComponents';

View File

@@ -24,7 +24,7 @@ import { useTranslation } from 'react-i18next';
import { MailReceiver, RegistInputRow } from '../../styles/ModuleComponents'; import { MailReceiver, RegistInputRow } from '../../styles/ModuleComponents';
import AuthModal from '../../components/common/modal/AuthModal'; import AuthModal from '../../components/common/modal/AuthModal';
import { authType } from '../../assets/data'; import { authType } from '../../assets/data';
import { useDataFetch } from '../../utils/hook'; import { useDataFetch } from '../../hooks/hook';
import { BattleConfigView } from '../../apis/Battle'; import { BattleConfigView } from '../../apis/Battle';
import { currencyCodeTypes } from '../../assets/data/types'; import { currencyCodeTypes } from '../../assets/data/types';
import { DynamicModal } from '../../components/common'; import { DynamicModal } from '../../components/common';
@@ -291,7 +291,6 @@ const MailRegist = () => {
await MailSingleRegist(token, resultData).then(data => { await MailSingleRegist(token, resultData).then(data => {
setLoading(false); setLoading(false);
handleSubmitModal(); handleSubmitModal();
console.log(data);
if(data.result === "ERROR"){ if(data.result === "ERROR"){
if(data.data.message === "ERROR_MAIL_ITEM_CALIUM_OVER"){ if(data.data.message === "ERROR_MAIL_ITEM_CALIUM_OVER"){
setAlertMsg(t('MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING')); setAlertMsg(t('MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING'));

View File

@@ -7,7 +7,7 @@ import Pagination from '../../components/common/Pagination/Pagination';
import Modal from '../../components/common/modal/Modal'; import Modal from '../../components/common/modal/Modal';
import { AdminViewList, AdminViewGroupList, AdminLoginApprove, AdminDeleteUser, AdminChangeGroup, AdminChangePw } from '../../apis/Admin'; import { AdminViewList, AdminViewGroupList, AdminLoginApprove, AdminDeleteUser, AdminChangeGroup, AdminChangePw } from '../../apis/Admin';
import AdminViewSearchBar from '../../components/UserManage/AdminViewSearchBar'; import {AdminViewSearchBar} from '../../components/ServiceManage';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { authList } from '../../store/authList'; import { authList } from '../../store/authList';

View File

@@ -191,14 +191,17 @@ const useAuthSetting = (initialId) => {
// menuConfig를 기반으로 권한 그룹 구조화 // menuConfig를 기반으로 권한 그룹 구조화
const authGroups = useMemo(() => { const authGroups = useMemo(() => {
return Object.entries(menuConfig).map(([key, section]) => ({ return Object.entries(menuConfig)
id: key, .map(([key, section]) => ({
title: section.title, id: key,
items: Object.entries(section.items).map(([itemKey, item]) => ({ title: section.title,
id: itemKey, items: Object.entries(section.items)
title: item.title, .filter(([_, section]) => section.view === true)
permissions: item.permissions .map(([itemKey, item]) => ({
})) id: itemKey,
title: item.title,
permissions: item.permissions
}))
})); }));
}, []); }, []);

View File

@@ -19,10 +19,11 @@ import {
} from '../../styles/ModuleComponents'; } from '../../styles/ModuleComponents';
import {Button, ExcelDownButton, Pagination, DynamicModal, ViewTableInfo, Loading} from '../../components/common'; import {Button, ExcelDownButton, Pagination, DynamicModal, ViewTableInfo, Loading} from '../../components/common';
import { convertKTC, truncateText } from '../../utils'; import { convertKTC, truncateText } from '../../utils';
import { CaliumRequestRegistModal, CaliumRequestSearchBar } from '../../components/UserManage'; import { CaliumRequestRegistModal } from '../../components/UserManage';
import { CaliumCharge, CaliumRequestView } from '../../apis'; import { CaliumCharge, CaliumRequestView } from '../../apis';
import { withAuth } from '../../utils/hook'; import { withAuth } from '../../hooks/hook';
import { convertEndDateToISO, convertStartDateToISO } from '../../utils/date'; import { convertEndDateToISO, convertStartDateToISO } from '../../utils/date';
import {CaliumRequestSearchBar} from '../../components/ServiceManage';
const CaliumRequest = () => { const CaliumRequest = () => {
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');

View File

@@ -0,0 +1,257 @@
import React, { useState, Fragment, useMemo } from 'react';
import Button from '../../components/common/button/Button';
import Loading from '../../components/common/Loading';
import {
Title,
SelectInput,
TableStyle,
TableWrapper,
TableActionButton,
TableDetailRow,
TableDetailContainer,
TableDetailFlex,
TableDetailColumn,
DetailTableInfo,
} from '../../styles/Components';
import { modalTypes } from '../../assets/data';
import { useTranslation } from 'react-i18next';
import {
FormLabel,
FormRowGroup, MessageWrapper,
} from '../../styles/ModuleComponents';
import { authType } from '../../assets/data';
import { useModal, withAuth } from '../../hooks/hook';
import { DynamicModal, TopButton } from '../../components/common';
import { opInitDataType, opSuccessType } from '../../assets/data/options';
import { InitData } from '../../apis/Data';
import DataInitSearchBar, { useDataInitSearch } from '../../components/ServiceManage/searchBar/DataInitSearchBar';
const DataInitView = () => {
const { t } = useTranslation();
const token = sessionStorage.getItem('token');
const [loading, setLoading] = useState(false); // 로딩 창
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
cancel: 'hidden',
registConfirm: 'hidden',
registComplete: 'hidden'
});
const [alertMsg, setAlertMsg] = useState('');
const [resultData, setResultData] = useState(initData); //데이터 정보
const [expandedRows, setExpandedRows] = useState({});
const {
searchParams,
loading: dataLoading,
data: dataList,
handleSearch,
handleReset,
updateSearchParams
} = useDataInitSearch(token, setAlertMsg);
const toggleRowExpand = (index) => {
setExpandedRows(prev => ({
...prev,
[index]: !prev[index]
}));
};
const tableHeaders = useMemo(() => {
return [
{ id: 'logTime', label: '일시', width: '100px' },
{ id: 'key', label: '키', width: '150px' },
{ id: 'dataType', label: '초기화대상', width: '80px' },
{ id: 'tranId', label: '트랜잭션ID', width: '150px' },
{ id: 'success', label: '성공여부', width: '60px' },
{ id: 'message', label: '작업메시지', width: '300px' },
{ id: 'details', label: '상세정보', width: '100px' },
// { id: 'recovery', label: '복구', width: '100px' }
];
}, []);
const renderDetailData = (data) => {
if (!data || typeof data !== 'object') return <></>;
return (
<DetailTableInfo>
<thead>
<tr>
<th colSpan="2">정보</th>
</tr>
</thead>
<tbody>
{Object.entries(data).map(([key, value]) => (
<tr key={`${key}`}>
<td>{key}</td>
<td>
{typeof value === 'object' && value !== null
? JSON.stringify(value)
: String(value)
}
</td>
</tr>
))}
</tbody>
</DetailTableInfo>
);
};
const handleSubmit = async (type, param = null) => {
switch (type) {
case "submit":
handleModalView('registConfirm');
break;
case "registConfirm":
setLoading(true);
await InitData(token, resultData).then(data => {
handleModalClose('registConfirm');
if(data.result === "SUCCESS") {
handleModalView('registComplete');
}else{
setAlertMsg(t('REGIST_FAIL'));
}
}).catch(reason => {
handleModalClose('registConfirm');
setAlertMsg(t('API_FAIL'));
}).finally(() => {
setLoading(false);
});
break;
case "registComplete":
dataReset();
handleModalClose('registComplete');
break;
case "warning":
setAlertMsg('');
break;
}
}
const dataReset = () => {
setResultData(initData);
}
return (
<>
<Title>데이터 초기화</Title>
<MessageWrapper>
<FormRowGroup>
<FormLabel>초기화 대상</FormLabel>
<SelectInput value={resultData?.init_data_type} onChange={e => setResultData({ ...resultData, init_data_type: e.target.value })} width="150px">
{opInitDataType.map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput>
<Button
text="초기화"
theme={'primary'}
type="submit"
size="large"
width="100px"
handleClick={() => handleSubmit('submit')}
/>
</FormRowGroup>
</MessageWrapper>
<MessageWrapper>
<DataInitSearchBar
searchParams={searchParams}
onSearch={(newParams, executeSearch = true) => {
if (executeSearch) {
handleSearch(newParams);
} else {
updateSearchParams(newParams);
}
}}
onReset={handleReset}
/>
</MessageWrapper>
<TableWrapper>
<TableStyle>
<thead>
<tr>
{tableHeaders.map(header => (
<th key={header.id} width={header.width}>{header.label}</th>
))}
</tr>
</thead>
<tbody>
{dataList?.init_list?.map((item, index) => (
<Fragment key={index}>
<tr>
<td>{item.timestamp}</td>
<td>{item.key}</td>
<td>{opInitDataType.find(type => type.value === item.initDataType)?.name}</td>
<td>{item.tranId}</td>
<td>{opSuccessType.find(type => type.value === item.success)?.name}</td>
<td>{item.message}</td>
<td>
<TableActionButton onClick={() => toggleRowExpand(index)}>
{expandedRows[index] ? '접기' : '상세보기'}
</TableActionButton>
</td>
</tr>
{expandedRows[index] && (
<TableDetailRow>
<td colSpan={tableHeaders.length}>
<TableDetailContainer>
<TableDetailFlex>
<TableDetailColumn>
{renderDetailData(item.data)}
</TableDetailColumn>
</TableDetailFlex>
</TableDetailContainer>
</td>
</TableDetailRow>
)}
</Fragment>
))}
</tbody>
</TableStyle>
</TableWrapper>
{/* 확인 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.registConfirmModal}
modalText={t('DATA_INIT_CONFIRM',{data:opInitDataType.find(type => type.value === resultData.init_data_type).name})}
handleSubmit={() => handleSubmit('registConfirm')}
handleCancel={() => handleModalClose('registConfirm')}
/>
{/* 완료 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.registCompleteModal}
modalText={t('INIT_COMPLTE')}
handleSubmit={() => handleSubmit('registComplete')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => setAlertMsg('')}
/>
{(loading || dataLoading) && <Loading/>}
<TopButton />
</>
);
};
export default withAuth(authType.levelMaster)(DataInitView);
const initData = {
init_data_type: 'None'
}

View File

@@ -1,6 +1,6 @@
import { Fragment, useState, useEffect } from 'react'; import { Fragment, useState, useEffect } from 'react';
import LogViewSearchBar from '../../components/UserManage/LogViewSearchBar'; import {LogViewSearchBar} from '../../components/ServiceManage';
import { Title, FormWrapper, SelectInput, TableInfo, ListCount, ListOption, TableStyle, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components'; import { Title, FormWrapper, SelectInput, TableInfo, ListCount, ListOption, TableStyle, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
import Button from '../../components/common/button/Button'; import Button from '../../components/common/button/Button';
import Pagination from '../../components/common/Pagination/Pagination'; import Pagination from '../../components/common/Pagination/Pagination';

View File

@@ -3,4 +3,4 @@ export { default as LogView } from './LogView';
export { default as AuthSetting } from './AuthSetting'; export { default as AuthSetting } from './AuthSetting';
export { default as AuthSettingUpdate } from './AuthSettingUpdate'; export { default as AuthSettingUpdate } from './AuthSettingUpdate';
export { default as CaliumRequest} from './CaliumRequest'; export { default as CaliumRequest} from './CaliumRequest';
export { default as CaliumRequestRegist} from '../../components/UserManage/CaliumRequestRegistModal'; export { default as DataInitView} from './DataInitView';

View File

@@ -78,6 +78,17 @@ export const SelectInput = styled.select`
} }
`; `;
export const FormCenteredContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: calc(100vw - 400px);
height: calc(100vh - 500px); /* 상단 영역 높이에 따라 조정 (헤더+필터 영역) */
padding: 20px;
overflow: auto;
min-height: 100px;
`;
export const Textarea = styled.textarea` export const Textarea = styled.textarea`
&:focus { &:focus {
border: 1px solid #2c2c2c; border: 1px solid #2c2c2c;
@@ -150,6 +161,7 @@ export const BtnWrapper = styled.div`
justify-content: ${props => props.$justify}; justify-content: ${props => props.$justify};
gap: ${props => props.$gap}; gap: ${props => props.$gap};
margin-top: ${props => props.$marginTop}; margin-top: ${props => props.$marginTop};
margin-bottom: ${props => props.$marginBottom};
padding-top: ${props => props.$paddingTop}; padding-top: ${props => props.$paddingTop};
`; `;
@@ -548,3 +560,62 @@ export const PopupMessage = styled(Link)`
color: #61a2d0; color: #61a2d0;
text-decoration: underline; text-decoration: underline;
`; `;
export const TableDetailRow = styled.tr`
background-color: #f8f9fa;
`;
export const TableDetailContainer = styled.div`
padding: 15px;
background-color: #f8f9fa;
`;
export const TableDetailFlex = styled.div`
display: flex;
gap: 20px;
flex-wrap: wrap;
`;
export const TableDetailColumn = styled.div`
flex: 1 1 48%;
min-width: 300px;
`;
export const DetailTableInfo = styled.table`
width: 100%;
border-collapse: collapse;
margin-bottom: 15px;
font-size: 12px;
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f1f1f1;
font-weight: bold;
}
tr:nth-child(even) {
background-color: #f8f8f8;
}
`;
export const TableActionButton = styled.button`
cursor: pointer;
background: #4a89dc;
color: white;
border: none;
border-radius: 3px;
padding: 2px 5px;
font-size: 13px;
min-width: max-content;
width: 80px;
height: 24px;
&:hover {
background: #3a70bc;
}
`;

View File

@@ -569,6 +569,13 @@ export const MessageBox = styled.div`
// FORM // FORM
export const FormItemGroup = styled.div`
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: flex-start;
`;
export const FormGroup = styled.div` export const FormGroup = styled.div`
margin-bottom: 24px; margin-bottom: 24px;
`; `;
@@ -576,17 +583,20 @@ export const FormGroup = styled.div`
export const FormRowGroup = styled.div` export const FormRowGroup = styled.div`
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 20px 40px; gap: 10px 40px;
margin-bottom: 24px; margin-bottom: 24px;
`; `;
export const FormLabel = styled.label` export const FormLabel = styled.label`
display: block; //display: block;
font-size: 16px; font-size: 16px;
margin-bottom: 8px; margin-bottom: 8px;
min-width: fit-content; min-width: fit-content;
font-weight: 700; font-weight: 700;
line-height: 25px; line-height: 25px;
display: flex;
align-items: center;
justify-content: flex-start;
`; `;
export const FormInput = styled.input` export const FormInput = styled.input`

View File

@@ -1,16 +1,28 @@
import { menuConfig } from '../assets/data/menuConfig'; import { menuConfig } from '../assets/data/menuConfig';
export const getMenuConfig = (userInfo) => { export const getMenuConfig = (userInfo) => {
return Object.entries(menuConfig).map(([key, group]) => ({ const isLiveEnv = process.env.REACT_APP_ENV === 'live';
title: group.title, return Object.entries(menuConfig)
link: `/${key}`, .map(([key, group]) => {
access: hasGroupAccess(userInfo, group), return {
submenu: Object.entries(group.items).map(([itemKey, item]) => ({ title: group.title,
title: item.title, link: `/${key}`,
link: `/${key}/${itemKey}`, access: hasGroupAccess(userInfo, group),
id: item.permissions.read submenu: Object.entries(group.items)
})) .filter(([itemKey, item]) => {
})); if(isLiveEnv) {
if(item.test && item.test === true) return false;
}
return true;
})
.map(([itemKey, item]) => ({
title: item.title,
link: `/${key}/${itemKey}`,
id: item.permissions.read,
authLevel: item.authLevel
}))
}
});
}; };
// 권한 체크 유틸리티 함수들 // 권한 체크 유틸리티 함수들