Compare commits
3 Commits
3e5c3f0167
...
065c081a85
| Author | SHA1 | Date | |
|---|---|---|---|
| 065c081a85 | |||
| fa290b64ec | |||
| f8d5b2197d |
@@ -24,9 +24,9 @@ import {
|
||||
ReportList,
|
||||
UserBlock,
|
||||
UserBlockRegist,
|
||||
WhiteList,
|
||||
LandAuction,
|
||||
BattleEvent, MenuBanner, MenuBannerRegist,
|
||||
BattleEvent,
|
||||
MenuBanner, MenuBannerRegist,
|
||||
} from './pages/ServiceManage';
|
||||
|
||||
const RouteInfo = () => {
|
||||
@@ -64,7 +64,6 @@ const RouteInfo = () => {
|
||||
</Route>
|
||||
<Route path="/servicemanage">
|
||||
<Route path="board" element={<Board />} />
|
||||
<Route path="whitelist" element={<WhiteList />} />
|
||||
<Route path="mail" element={<Mail />} />
|
||||
<Route path="mail/mailregist" element={<MailRegist />} />
|
||||
<Route path="userblock" element={<UserBlock />} />
|
||||
|
||||
@@ -2,26 +2,31 @@
|
||||
|
||||
import { Axios } from '../utils';
|
||||
|
||||
//아이템 리스트 조회
|
||||
export const ItemListView = async (token, searchType, data, status, restore, order, size, currentPage) => {
|
||||
export const ItemListAPI = async (token, params) => {
|
||||
try {
|
||||
const res = await Axios.get(
|
||||
`/api/v1/items/list?search_type=${searchType ? searchType : ''}
|
||||
&search_key=${data ? data : ''}
|
||||
&orderby=${order}
|
||||
&page_no=${currentPage}
|
||||
&page_size=${size}
|
||||
`,
|
||||
{
|
||||
const res = await Axios.post(`/api/v1/items/list`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
// console.log(res.data.data);
|
||||
});
|
||||
|
||||
return res.data.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
|
||||
throw new Error('ItemAPI Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const ItemDeleteAPI = async (token, params) => {
|
||||
try {
|
||||
const res = await Axios.delete(`/api/v1/items/delete`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
data: params,
|
||||
});
|
||||
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('ItemDelete Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,134 +0,0 @@
|
||||
//운영서비스 관리 - 화이트 리스트 api 연결
|
||||
|
||||
import { Axios } from '../utils';
|
||||
|
||||
export const WhiteListData = async token => {
|
||||
try {
|
||||
const res = await Axios.get(`/api/v1/white-list/list`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data.data.list;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('whiteList Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 선택 삭제
|
||||
export const WhiteListDelete = async (token, params) => {
|
||||
try {
|
||||
const res = await Axios.delete(`/api/v1/white-list`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
data: { list: params },
|
||||
});
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('WhiteListDelete', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 선택 승인
|
||||
export const WhiteListAllow = async (token, params) => {
|
||||
try {
|
||||
const res = await Axios.patch(
|
||||
`/api/v1/white-list`,
|
||||
{ list: params },
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
return res;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('WhiteListAllow', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 화이트 리스트 등록 (단일)
|
||||
export const WhiteListRegist = async (token, params) => {
|
||||
try {
|
||||
const res = await Axios.post(`/api/v1/white-list`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
return res;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('WhiteListRegist', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 화이트리스트 엑셀 업로더
|
||||
export const WhiteListExelUpload = async (token, file) => {
|
||||
const exelFile = new FormData();
|
||||
exelFile.append('file', file);
|
||||
try {
|
||||
const res = await Axios.post(`/api/v1/white-list/excel-upload`, exelFile, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('WhiteListExelUpload', e);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 화이트 리스트 등록(복수) -> 등록하는 것임
|
||||
export const WhiteListMultiRegsit = async (token, file) => {
|
||||
const exelFile = new FormData();
|
||||
exelFile.append('file', file);
|
||||
try {
|
||||
const res = await Axios.post(`/api/v1/white-list/multiPost`, exelFile, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('WhiteListMultiRegsit', e);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 엑셀 다운로드
|
||||
export const WhiteListExport = async (token, fileName) => {
|
||||
try{
|
||||
await Axios.get(`/api/v1/white-list/excelDownLoad`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
responseType: 'blob',
|
||||
}).then(response => {
|
||||
const href = URL.createObjectURL(response.data);
|
||||
|
||||
const link = document.createElement('a');
|
||||
const fileName = 'Caliverse_whitelist.xlsx';
|
||||
link.href = href;
|
||||
link.setAttribute('download', `${fileName}`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(href);
|
||||
})
|
||||
|
||||
}catch(e) {
|
||||
if(e instanceof Error) {
|
||||
throw new Error('WhiteListExport Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,10 +1,13 @@
|
||||
import { createAPIModule } from '../utils/apiService';
|
||||
|
||||
import * as APIConfigs from '../assets/data/apis'
|
||||
|
||||
export * from './Admin';
|
||||
export * from './Auth';
|
||||
export * from './Group';
|
||||
export * from './History';
|
||||
export * from './Mail';
|
||||
export * from './Notice';
|
||||
export * from './WhiteList';
|
||||
export * from './BlackList';
|
||||
export * from './Users';
|
||||
export * from './Indicators';
|
||||
@@ -14,3 +17,21 @@ export * from './Calium';
|
||||
export * from './Land';
|
||||
export * from './Menu';
|
||||
export * from './OpenAI';
|
||||
|
||||
const apiModules = {};
|
||||
const allApis = {};
|
||||
|
||||
// 각 API 설정에 대해 모듈 생성
|
||||
Object.entries(APIConfigs).forEach(([configName, config]) => {
|
||||
const moduleName = configName.replace(/API$/, ''); // "userAPI" -> "user"
|
||||
apiModules[moduleName] = createAPIModule(config);
|
||||
|
||||
// 모든 API 함수 추출해서 allApis에 복사
|
||||
Object.entries(apiModules[moduleName]).forEach(([fnName, fn]) => {
|
||||
allApis[fnName] = fn;
|
||||
});
|
||||
});
|
||||
|
||||
export const Modules = apiModules;
|
||||
|
||||
// export const ItemAPI = createAPIModule(itemAPIConfig);
|
||||
7
src/assets/data/apis/index.js
Normal file
7
src/assets/data/apis/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import itemAPI from './itemAPI.json';
|
||||
import menuBannerAPI from './menuBannerAPI.json';
|
||||
|
||||
export {
|
||||
itemAPI,
|
||||
menuBannerAPI
|
||||
};
|
||||
17
src/assets/data/apis/itemAPI.json
Normal file
17
src/assets/data/apis/itemAPI.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"baseUrl": "/api/v1/items",
|
||||
"endpoints": {
|
||||
"ItemList": {
|
||||
"method": "POST",
|
||||
"url": "/list",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "body"
|
||||
},
|
||||
"ItemDelete": {
|
||||
"method": "DELETE",
|
||||
"url": "/delete",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "body"
|
||||
}
|
||||
}
|
||||
}
|
||||
48
src/assets/data/apis/menuBannerAPI.json
Normal file
48
src/assets/data/apis/menuBannerAPI.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"baseUrl": "/api/v1/menu",
|
||||
"endpoints": {
|
||||
"MenuBannerView": {
|
||||
"method": "GET",
|
||||
"url": "/banner/list",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "query"
|
||||
},
|
||||
"MenuBannerDetailView": {
|
||||
"method": "GET",
|
||||
"url": "/banner/detail/:id",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "query",
|
||||
"paramMapping": ["id"]
|
||||
},
|
||||
"MenuBannerSingleRegist": {
|
||||
"method": "POST",
|
||||
"url": "/banner",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "body"
|
||||
},
|
||||
"MenuBannerModify": {
|
||||
"method": "PUT",
|
||||
"url": "/banner/:id",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "body"
|
||||
},
|
||||
"MenuBannerDelete": {
|
||||
"method": "DELETE",
|
||||
"url": "/banner/delete",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "body"
|
||||
},
|
||||
"MenuImageUpload": {
|
||||
"method": "POST",
|
||||
"url": "/image-upload",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "body"
|
||||
},
|
||||
"MenuImageDelete": {
|
||||
"method": "DELETE",
|
||||
"url": "/image-delete",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "body"
|
||||
}
|
||||
}
|
||||
}
|
||||
131
src/assets/data/apis/sampleAPI.json
Normal file
131
src/assets/data/apis/sampleAPI.json
Normal file
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"baseUrl": "/api/v1/users",
|
||||
"endpoints": {
|
||||
"UserView": {
|
||||
"method": "GET",
|
||||
"url": "/api/v1/users/find-users",
|
||||
"dataPath": "data.data.result",
|
||||
"paramFormat": "query",
|
||||
"paramMapping": ["search_type", "search_key"]
|
||||
},
|
||||
"UserInfoView": {
|
||||
"method": "GET",
|
||||
"url": "/api/v1/users/basicinfo",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "query",
|
||||
"paramMapping": ["guid"]
|
||||
},
|
||||
"UserChangeNickName": {
|
||||
"method": "PUT",
|
||||
"url": "/api/v1/users/change-nickname",
|
||||
"dataPath": null,
|
||||
"paramFormat": "body",
|
||||
"paramMapping": ["guid", "nickname"]
|
||||
},
|
||||
"UserChangeAdminLevel": {
|
||||
"method": "PUT",
|
||||
"url": "/api/v1/users/change-level",
|
||||
"dataPath": null,
|
||||
"paramFormat": "body",
|
||||
"paramMapping": ["guid", "level"]
|
||||
},
|
||||
"UserKick": {
|
||||
"method": "PUT",
|
||||
"url": "/api/v1/users/user-kick",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "body",
|
||||
"paramMapping": ["guid"]
|
||||
},
|
||||
"UserAvatarView": {
|
||||
"method": "GET",
|
||||
"url": "/api/v1/users/avatarinfo",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "query",
|
||||
"paramMapping": ["guid"]
|
||||
},
|
||||
"UserClothView": {
|
||||
"method": "GET",
|
||||
"url": "/api/v1/users/clothinfo",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "query",
|
||||
"paramMapping": ["guid"]
|
||||
},
|
||||
"UserToolView": {
|
||||
"method": "GET",
|
||||
"url": "/api/v1/users/toolslot",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "query",
|
||||
"paramMapping": ["guid"]
|
||||
},
|
||||
"UserInventoryView": {
|
||||
"method": "GET",
|
||||
"url": "/api/v1/users/inventory",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "query",
|
||||
"paramMapping": ["guid"]
|
||||
},
|
||||
"UserInventoryItemDelete": {
|
||||
"method": "DELETE",
|
||||
"url": "/api/v1/users/inventory/delete/item",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "body",
|
||||
"paramMapping": ["guid", "inventory_id"]
|
||||
},
|
||||
"UserTattooView": {
|
||||
"method": "GET",
|
||||
"url": "/api/v1/users/tattoo",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "query",
|
||||
"paramMapping": ["guid"]
|
||||
},
|
||||
"UserQuestView": {
|
||||
"method": "GET",
|
||||
"url": "/api/v1/users/quest",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "query",
|
||||
"paramMapping": ["guid"]
|
||||
},
|
||||
"UserFriendListView": {
|
||||
"method": "GET",
|
||||
"url": "/api/v1/users/friendlist",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "query",
|
||||
"paramMapping": ["guid"]
|
||||
},
|
||||
"UserMailView": {
|
||||
"method": "POST",
|
||||
"url": "/api/v1/users/mail",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "body",
|
||||
"paramMapping": ["guid", "page", "limit"]
|
||||
},
|
||||
"UserMailDelete": {
|
||||
"method": "DELETE",
|
||||
"url": "/api/v1/users/mail/delete",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "body",
|
||||
"paramMapping": ["mail_id"]
|
||||
},
|
||||
"UserMailItemDelete": {
|
||||
"method": "DELETE",
|
||||
"url": "/api/v1/users/mail/delete/item",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "body",
|
||||
"paramMapping": ["mail_id", "item_id"]
|
||||
},
|
||||
"UserMailDetailView": {
|
||||
"method": "GET",
|
||||
"url": "/api/v1/users/mail/:id",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "path",
|
||||
"paramMapping": ["id"]
|
||||
},
|
||||
"UserMyhomeView": {
|
||||
"method": "GET",
|
||||
"url": "/api/v1/users/myhome",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "query",
|
||||
"paramMapping": ["guid"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,16 +194,26 @@ export const menuConfig = {
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
menubanner: {
|
||||
title: '메뉴 배너 관리',
|
||||
items: {
|
||||
title: '아이템 관리',
|
||||
permissions: {
|
||||
read: authType.menuBannerRead,
|
||||
update: authType.menuBannerUpdate,
|
||||
delete: authType.menuBannerDelete
|
||||
read: authType.itemRead,
|
||||
update: authType.itemUpdate,
|
||||
delete: authType.itemDelete
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
// menubanner: {
|
||||
// title: '메뉴 배너 관리',
|
||||
// permissions: {
|
||||
// read: authType.menuBannerRead,
|
||||
// update: authType.menuBannerUpdate,
|
||||
// delete: authType.menuBannerDelete
|
||||
// },
|
||||
// view: true,
|
||||
// authLevel: adminAuthLevel.NONE
|
||||
// },
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -45,10 +45,10 @@ export const mailReceiveType = [
|
||||
];
|
||||
|
||||
export const adminLevelType = [
|
||||
{ value: '0', name: '없음' },
|
||||
{ value: '1', name: 'GM' },
|
||||
{ value: '2', name: 'Super GM' },
|
||||
{ value: '3', name: 'Developer' },
|
||||
{ value: 0, name: '없음' },
|
||||
{ value: 1, name: 'GM' },
|
||||
{ value: 2, name: 'Super GM' },
|
||||
{ value: 3, name: 'Developer' },
|
||||
]
|
||||
|
||||
export const eventStatus = [
|
||||
@@ -297,6 +297,39 @@ export const opMenuBannerStatus = [
|
||||
{ value: 'FINISH', name: '만료' },
|
||||
];
|
||||
|
||||
export const opItemStatus = [
|
||||
{ value: 'ALL', name: '전체' },
|
||||
{ value: 'ACTIVE', name: '활성' },
|
||||
{ value: 'DEACTIVE', name: '비활성' },
|
||||
];
|
||||
|
||||
export const opItemRestore = [
|
||||
{ value: 'ALL', name: '전체' },
|
||||
{ value: 'POSSIBLE', name: '가능' },
|
||||
{ value: 'IMPOSSIBLE', name: '불가능' },
|
||||
];
|
||||
|
||||
export const opEquipType = [
|
||||
{ value: 0, name: '미장착' },
|
||||
{ value: 1, name: '의상장착' },
|
||||
{ value: 2, name: '도구장착' },
|
||||
{ value: 3, name: '타투장착' },
|
||||
]
|
||||
|
||||
export const opItemType = [
|
||||
{ value: 'TOOL', name: '도구' },
|
||||
{ value: 'EXPENDABLE', name: '소모품' },
|
||||
{ value: 'TICKET', name: '티켓' },
|
||||
{ value: 'RAND_BOX', name: '랜덤 박스' },
|
||||
{ value: 'CLOTH', name: '의상' },
|
||||
{ value: 'AVATAR', name: '아바타' },
|
||||
{ value: 'PROP', name: '프랍' },
|
||||
{ value: 'TATTOO', name: '타투' },
|
||||
{ value: 'CURRENCY', name: '재화' },
|
||||
{ value: 'PRODUCT', name: '제품' },
|
||||
{ value: 'BEAUTY', name: '뷰티' },
|
||||
]
|
||||
|
||||
// export const logAction = [
|
||||
// { value: "None", name: "ALL" },
|
||||
// { value: "AIChatDeleteCharacter", name: "NPC 삭제" },
|
||||
@@ -812,70 +845,9 @@ export const logAction = [
|
||||
|
||||
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: "AIChat", name: "AIChat" },
|
||||
{ value: "AuthLogInOut", name: "AuthLogInOut" },
|
||||
{ value: "BuildingProfit", name: "BuildingProfit" },
|
||||
{ value: "BattleObjectInteraction", name: "BattleObjectInteraction" },
|
||||
{ value: "BattleObjectStateUpdate", name: "BattleObjectStateUpdate" },
|
||||
@@ -884,5 +856,66 @@ export const logDomain = [
|
||||
{ value: "BattleRoomJoin", name: "BattleRoomJoin" },
|
||||
{ value: "BattleDead", name: "BattleDead" },
|
||||
{ value: "BattleRound", name: "BattleRound" },
|
||||
{ value: "BattleSnapshot", name: "BattleSnapshot" }
|
||||
{ value: "BattleSnapshot", name: "BattleSnapshot" },
|
||||
{ value: "BeaconCreate", name: "BeaconCreate" },
|
||||
{ value: "Beacon", name: "Beacon" },
|
||||
{ value: "BrokerApi", name: "BrokerApi" },
|
||||
{ value: "Buff", name: "Buff" },
|
||||
{ value: "Building", name: "Building" },
|
||||
{ value: "Calium", name: "Calium" },
|
||||
{ value: "CaliumEchoSystem", name: "CaliumEchoSystem" },
|
||||
{ value: "CaliumStorageFail", name: "CaliumStorageFail" },
|
||||
{ value: "Character", name: "Character" },
|
||||
{ value: "CharacterCreate", name: "CharacterCreate" },
|
||||
{ value: "Chat", name: "Chat" },
|
||||
{ value: "CheatRenewalShopProducts", name: "CheatRenewalShopProducts" },
|
||||
{ value: "ChangeDanceEntityState", name: "ChangeDanceEntityState" },
|
||||
{ value: "ClaimReward", name: "ClaimReward" },
|
||||
{ value: "Craft", name: "Craft" },
|
||||
{ value: "CraftHelp", name: "CraftHelp" },
|
||||
{ value: "Cart", name: "Cart" },
|
||||
{ value: "Currency", name: "Currency" },
|
||||
{ value: "CustomDefineUi", name: "CustomDefineUi" },
|
||||
{ value: "EscapePosition", name: "EscapePosition" },
|
||||
{ value: "Friend", name: "Friend" },
|
||||
{ value: "Farming", name: "Farming" },
|
||||
{ value: "FarmingReward", name: "FarmingReward" },
|
||||
{ value: "GameLogInOut", name: "GameLogInOut" },
|
||||
{ value: "Item", name: "Item" },
|
||||
{ value: "MyHome", name: "MyHome" },
|
||||
{ value: "Land", name: "Land" },
|
||||
{ value: "LandAuction", name: "LandAuction" },
|
||||
{ value: "LandAuctionActivity", name: "LandAuctionActivity" },
|
||||
{ value: "LandAuctionBid", name: "LandAuctionBid" },
|
||||
{ value: "LandAuctionBidPriceRefund", name: "LandAuctionBidPriceRefund" },
|
||||
{ value: "Mail", name: "Mail" },
|
||||
{ value: "MailStoragePeriodExpired", name: "MailStoragePeriodExpired" },
|
||||
{ value: "MailProfile", name: "MailProfile" },
|
||||
{ value: "Party", name: "Party" },
|
||||
{ value: "PartyMember", name: "PartyMember" },
|
||||
{ value: "PartyVote", name: "PartyVote" },
|
||||
{ value: "PartyInstance", name: "PartyInstance" },
|
||||
{ value: "Position", name: "Position" },
|
||||
{ value: "PackageLastOrderRecode", name: "PackageLastOrderRecode" },
|
||||
{ value: "PackageRepeat", name: "PackageRepeat" },
|
||||
{ value: "PackageState", name: "PackageState" },
|
||||
{ value: "QuestMain", name: "QuestMain" },
|
||||
{ value: "QuestUgq", name: "QuestUgq" },
|
||||
{ value: "QuestMail", name: "QuestMail" },
|
||||
{ value: "RenewalShopProducts", name: "RenewalShopProducts" },
|
||||
{ value: "Rental", name: "Rental" },
|
||||
{ value: "RewardProp", name: "RewardProp" },
|
||||
{ value: "Stage", name: "Stage" },
|
||||
{ value: "SocialAction", name: "SocialAction" },
|
||||
{ value: "SeasonPass", name: "SeasonPass" },
|
||||
{ value: "Shop", name: "Shop" },
|
||||
{ value: "SwitchingProp", name: "SwitchingProp" },
|
||||
{ value: "Taxi", name: "Taxi" },
|
||||
{ value: "TaskReservation", name: "TaskReservation" },
|
||||
{ value: "UgqApi", name: "UgqApi" },
|
||||
{ value: "User", name: "User" },
|
||||
{ value: "UserCreate", name: "UserCreate" },
|
||||
{ value: "UserInitial", name: "UserInitial" },
|
||||
{ value: "UserBlock", name: "UserBlock" },
|
||||
{ value: "UserReport", name: "UserReport" },
|
||||
];
|
||||
|
||||
39
src/assets/data/pages/itemSearch.json
Normal file
39
src/assets/data/pages/itemSearch.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"initialSearchParams": {
|
||||
"searchType": "GUID",
|
||||
"searchData": "",
|
||||
"orderBy": "DESC",
|
||||
"pageSize": 50,
|
||||
"currentPage": 1,
|
||||
"lastEvaluatedKey": null
|
||||
},
|
||||
"searchFields": [
|
||||
{
|
||||
"type": "select",
|
||||
"id": "searchType",
|
||||
"label": "대상",
|
||||
"optionsRef": "userSearchType",
|
||||
"col": 1,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "searchData",
|
||||
"placeholder": "대상 입력",
|
||||
"width": "300px",
|
||||
"col": 1,
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
|
||||
"apiInfo": {
|
||||
"endpointName": "ItemList",
|
||||
"loadOnMount": false,
|
||||
"pageField": "page_no",
|
||||
"pageSizeField": "page_size",
|
||||
"orderField": "orderBy",
|
||||
"lastPageKeyField": "pageKey"
|
||||
},
|
||||
"paginationType": "dynamodb",
|
||||
"initSearch": false
|
||||
}
|
||||
90
src/assets/data/pages/itemTable.json
Normal file
90
src/assets/data/pages/itemTable.json
Normal file
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"id": "itemTable",
|
||||
"selection": {
|
||||
"type": "single",
|
||||
"idField": "id"
|
||||
},
|
||||
"header": {
|
||||
"countType": "total",
|
||||
"orderType": "desc",
|
||||
"pageType": "default",
|
||||
"buttons": [
|
||||
{
|
||||
"id": "delete",
|
||||
"text": "선택 삭제",
|
||||
"theme": "line",
|
||||
"disableWhen": "noSelection",
|
||||
"requiredAuth": "itemDelete",
|
||||
"action": "delete"
|
||||
},
|
||||
{
|
||||
"id": "restore",
|
||||
"text": "아이템 복구",
|
||||
"theme": "line",
|
||||
"disableWhen": "disable",
|
||||
"requiredAuth": "itemDelete",
|
||||
"action": "restore"
|
||||
}
|
||||
]
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"id": "checkbox",
|
||||
"type": "checkbox",
|
||||
"width": "40px",
|
||||
"title": ""
|
||||
},
|
||||
{
|
||||
"id": "item_name",
|
||||
"type": "text",
|
||||
"width": "20%",
|
||||
"title": "아이템명"
|
||||
},
|
||||
{
|
||||
"id": "item_id",
|
||||
"type": "text",
|
||||
"width": "20%",
|
||||
"title": "아이템 ID"
|
||||
},
|
||||
{
|
||||
"id": "count",
|
||||
"type": "text",
|
||||
"width": "80px",
|
||||
"title": "수량"
|
||||
},
|
||||
{
|
||||
"id": "item_type",
|
||||
"type": "option",
|
||||
"width": "120px",
|
||||
"title": "아이템 타입",
|
||||
"option_name": "opItemType"
|
||||
},
|
||||
{
|
||||
"id": "equip_type",
|
||||
"type": "option",
|
||||
"width": "80px",
|
||||
"title": "장착",
|
||||
"option_name": "opEquipType"
|
||||
},
|
||||
{
|
||||
"id": "equiped_pos",
|
||||
"type": "text",
|
||||
"width": "80px",
|
||||
"title": "슬롯"
|
||||
},
|
||||
{
|
||||
"id": "create_dt",
|
||||
"type": "date",
|
||||
"width": "100px",
|
||||
"title": "생성날짜(KST)",
|
||||
"format": {
|
||||
"type": "function",
|
||||
"name": "convertKTC"
|
||||
}
|
||||
}
|
||||
],
|
||||
"sort": {
|
||||
"defaultColumn": "row_num",
|
||||
"defaultDirection": "desc"
|
||||
}
|
||||
}
|
||||
@@ -35,19 +35,14 @@
|
||||
],
|
||||
|
||||
"apiInfo": {
|
||||
"functionName": "MenuBannerView",
|
||||
"endpointName": "MenuBannerView",
|
||||
"loadOnMount": true,
|
||||
"paramsMapping": [
|
||||
"searchData",
|
||||
"status",
|
||||
"paramTransforms": [
|
||||
{"param": "startDate", "transform": "toISOString"},
|
||||
{"param": "endDate", "transform": "toISOString"},
|
||||
"orderBy",
|
||||
"pageSize",
|
||||
"currentPage"
|
||||
{"param": "endDate", "transform": "toISOString"}
|
||||
],
|
||||
"pageField": "currentPage",
|
||||
"pageSizeField": "pageSize",
|
||||
"pageField": "page_no",
|
||||
"pageSizeField": "page_size",
|
||||
"orderField": "orderBy"
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
"type": "select",
|
||||
"id": "searchType",
|
||||
"label": "대상",
|
||||
"optionsRef": "eventStatus",
|
||||
"optionsRef": "userSearchType",
|
||||
"col": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,7 +8,9 @@ const UserAvatarInfo = ({ userInfo }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if(userInfo && Object.keys(userInfo).length > 0) {
|
||||
fetchData();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
|
||||
@@ -20,23 +20,25 @@ import { opUserSessionType } from '../../assets/data/options';
|
||||
import Button from '../common/button/Button';
|
||||
import { useModal } from '../../hooks/hook';
|
||||
import { InitData } from '../../apis/Data';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
|
||||
const UserDefaultInfo = ({ userInfo }) => {
|
||||
const { t } = useTranslation();
|
||||
const authInfo = useRecoilValue(authList);
|
||||
const token = sessionStorage.getItem('token');
|
||||
const {showModal, showToast} = useAlert();
|
||||
const {withLoading} = useLoading();
|
||||
|
||||
const {
|
||||
modalState,
|
||||
handleModalView,
|
||||
handleModalClose
|
||||
} = useModal({
|
||||
userKick: 'hidden',
|
||||
gmLevelChange: 'hidden',
|
||||
pwChange: 'hidden'
|
||||
});
|
||||
const [alertMsg, setAlertMsg] = useState('');
|
||||
const [dataList, setDataList] = useState({});
|
||||
const [adminLevel, setAdminLevel] = useState('0');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [authDelete, setAuthDelete] = useState(false);
|
||||
|
||||
@@ -45,7 +47,9 @@ const UserDefaultInfo = ({ userInfo }) => {
|
||||
}, [authInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
if(userInfo && Object.keys(userInfo).length > 0) {
|
||||
fetchData();
|
||||
}
|
||||
}, [userInfo]);
|
||||
|
||||
const fetchData = async () => {
|
||||
@@ -61,48 +65,46 @@ const UserDefaultInfo = ({ userInfo }) => {
|
||||
|
||||
switch (type) {
|
||||
case "gmLevelChangeSubmit":
|
||||
setAdminLevel(param);
|
||||
|
||||
handleModalView('gmLevelChange');
|
||||
showModal('USER_GM_CHANGE', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleSubmit('gmLevelChange', param)
|
||||
});
|
||||
break;
|
||||
|
||||
case "userKickSubmit":
|
||||
handleModalView('userKick');
|
||||
showModal('USER_KICK_CONFIRM', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleSubmit('userKick')
|
||||
});
|
||||
break;
|
||||
|
||||
case "gmLevelChange":
|
||||
setLoading(true);
|
||||
|
||||
params.guid = userInfo.guid;
|
||||
params.admin_level = adminLevel;
|
||||
params.admin_level = param;
|
||||
|
||||
await UserChangeAdminLevel(token, params).then(data =>{
|
||||
setAlertMsg(t('USER_GM_CHANGE_COMPLETE'))
|
||||
await withLoading(async () => {
|
||||
return await UserChangeAdminLevel(token, params);
|
||||
}).then(data =>{
|
||||
showToast('USER_GM_CHANGE_COMPLETE', {type: alertTypes.success});
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
showToast(error, {type: alertTypes.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'))
|
||||
await withLoading(async () => {
|
||||
return await UserKick(token, params);
|
||||
}).then((data) =>{
|
||||
showToast('USER_KICK_COMPLETE', {type: alertTypes.success});
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
showToast(error, {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
setLoading(false);
|
||||
handleModalClose('userKick');
|
||||
fetchData();
|
||||
});
|
||||
break;
|
||||
case "registComplete":
|
||||
handleModalClose('registComplete');
|
||||
break;
|
||||
case "warning":
|
||||
setAlertMsg('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,12 +133,13 @@ const UserDefaultInfo = ({ userInfo }) => {
|
||||
<tr>
|
||||
<th>접속상태</th>
|
||||
<StatusCell>{dataList.user_session !== undefined && opUserSessionType.find(session => session.value === dataList.user_session)?.name}
|
||||
{<Button theme={(dataList.user_session && authDelete) ? "line" : "disable"}
|
||||
{<Button
|
||||
theme={(dataList.user_session && authDelete) ? "line" : "disable"}
|
||||
id={"user_session"}
|
||||
name="kick"
|
||||
text="KICK"
|
||||
handleClick={() => handleSubmit('userKickSubmit')}
|
||||
disabled={!dataList.user_session && !authDelete}
|
||||
disabled={(!authDelete || !dataList.user_session)}
|
||||
/>}
|
||||
</StatusCell>
|
||||
</tr>
|
||||
@@ -163,7 +166,7 @@ const UserDefaultInfo = ({ userInfo }) => {
|
||||
<tr>
|
||||
<th>GM권한</th>
|
||||
<td>
|
||||
<SelectInput value={dataList.user_info && dataList.user_info.admin_level}
|
||||
<SelectInput value={dataList?.user_info?.admin_level}
|
||||
onChange={e => handleSubmit('gmLevelChangeSubmit', e.target.value)}
|
||||
disabled={authInfo.auth_list && !authInfo.auth_list.some(auth => auth.id === authType.userSearchUpdate)} >
|
||||
{adminLevelType.map((data, index) => (
|
||||
@@ -217,27 +220,6 @@ const UserDefaultInfo = ({ userInfo }) => {
|
||||
</UserInfoTable>
|
||||
</div>
|
||||
<NicknameChangeModal pwPop={modalState.pwChangeModal} handleClick={() => handleModalClose('pwChange')} dataList={dataList} />
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.gmLevelChangeModal}
|
||||
modalText={t('USER_GM_CHANGE')}
|
||||
handleSubmit={() => handleSubmit('gmLevelChange')}
|
||||
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('')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,9 @@ const UserDressInfo = ({ userInfo }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if(userInfo && Object.keys(userInfo).length > 0) {
|
||||
fetchData();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -13,7 +13,9 @@ const UserFriendInfo = ({ userInfo }) => {
|
||||
const [dataList, setDataList] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if(userInfo && Object.keys(userInfo).length > 0) {
|
||||
fetchData();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
|
||||
@@ -12,10 +12,16 @@ import { useRecoilValue } from 'recoil';
|
||||
import { authList } from '../../store/authList';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
import { InfoSubTitle, UserDefaultTable, UserTableWrapper } from '../../styles/ModuleComponents';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import { convertKTC, timeDiffMinute } from '../../utils';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
|
||||
const UserInventoryInfo = ({ userInfo }) => {
|
||||
const { t } = useTranslation();
|
||||
const authInfo = useRecoilValue(authList);
|
||||
const {showModal, showToast} = useAlert();
|
||||
const {withLoading} = useLoading();
|
||||
|
||||
const [dataList, setDataList] = useState();
|
||||
const [itemCount, setItemCount] = useState('');
|
||||
@@ -28,8 +34,10 @@ const UserInventoryInfo = ({ userInfo }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if(userInfo && Object.keys(userInfo).length > 0) {
|
||||
fetchData();
|
||||
}, []);
|
||||
}
|
||||
}, [userInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
setAuthDelete(authInfo.auth_list.some(auth => auth.id === 35));
|
||||
@@ -152,6 +160,19 @@ const UserInventoryInfo = ({ userInfo }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAction = async (action, item = null) => {
|
||||
switch (action) {
|
||||
case "delete":
|
||||
break;
|
||||
case "deleteConfirm":
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const ConfirmChild = () => {
|
||||
return(
|
||||
<InputItem>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { useState, Fragment, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import CheckBox from '../../components/common/input/CheckBox';
|
||||
import MailDetailModal from '../../components/DataManage/MailDetailModal';
|
||||
import { SelectInput, TextInput } from '../../styles/Components';
|
||||
import { UserMailDelete, UserMailItemDelete, UserMailView } from '../../apis';
|
||||
@@ -11,13 +9,12 @@ import ConfirmModal from '../common/modal/ConfirmModal';
|
||||
import CompletedModal from '../common/modal/CompletedModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CustomConfirmModal from '../common/modal/CustomConfirmModal';
|
||||
import DynamicModal from '../common/modal/DynamicModal';
|
||||
import { authType, ivenTabType, opMailType } from '../../assets/data';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { authList } from '../../store/authList';
|
||||
import { convertKTC } from '../../utils';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
import { eventSearchType, opPickupType, opReadType, opYNType } from '../../assets/data/options';
|
||||
import { opPickupType, opReadType, opYNType } from '../../assets/data/options';
|
||||
import { useDynamoDBPagination, useModal } from '../../hooks/hook';
|
||||
import { DynamoPagination } from '../common';
|
||||
|
||||
@@ -202,8 +199,7 @@ const UserMailInfo = ({ userInfo }) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList && dataList.mail_list &&
|
||||
dataList.mail_list.map((mail, idx) => {
|
||||
{dataList?.mail_list?.map((mail, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td>{convertKTC(mail.create_time)}</td>
|
||||
|
||||
@@ -15,7 +15,9 @@ const UserMyHomeInfo = ({ userInfo }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if(userInfo && Object.keys(userInfo).length > 0) {
|
||||
fetchData();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
|
||||
@@ -14,7 +14,9 @@ const UserQuestInfo = ({ userInfo }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if(userInfo && Object.keys(userInfo).length > 0) {
|
||||
fetchData();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
|
||||
@@ -9,7 +9,9 @@ const UserTatttooInfo = ({ userInfo }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if(userInfo && Object.keys(userInfo).length > 0) {
|
||||
fetchData();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
|
||||
@@ -9,7 +9,9 @@ const UserToolInfo = ({ userInfo }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if(userInfo && Object.keys(userInfo).length > 0) {
|
||||
fetchData();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
261
src/components/ServiceManage/ImageUploadBtn.js
Normal file
261
src/components/ServiceManage/ImageUploadBtn.js
Normal file
@@ -0,0 +1,261 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { MenuImageDelete, MenuImageUpload } from '../../apis';
|
||||
import { IMAGE_MAX_SIZE } from '../../assets/data/adminConstants';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
|
||||
const ImageUploadBtn = ({ disabled,
|
||||
onImageUpload,
|
||||
downloadData,
|
||||
disabledBtn,
|
||||
fileName,
|
||||
onFileDelete
|
||||
}) => {
|
||||
const [previewUrl, setPreviewUrl] = useState(null);
|
||||
const token = sessionStorage.getItem('token');
|
||||
const { showToast } = useAlert();
|
||||
|
||||
// 컴포넌트 언마운트 시 미리보기 URL 정리
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (previewUrl) {
|
||||
URL.revokeObjectURL(previewUrl);
|
||||
}
|
||||
};
|
||||
}, [previewUrl]);
|
||||
|
||||
const handleFile = async e => {
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (!file) return;
|
||||
|
||||
// 이미지 파일 확장자 체크
|
||||
const fileExt = file.name.split('.').pop().toLowerCase();
|
||||
if (fileExt !== 'png' && fileExt !== 'jpg' && fileExt !== 'jpeg') {
|
||||
showToast('FILE_IMAGE_EXTENSION_WARNING', {
|
||||
type: alertTypes.warning
|
||||
});
|
||||
if (document.querySelector('#fileinput')) {
|
||||
document.querySelector('#fileinput').value = '';
|
||||
}
|
||||
onFileDelete();
|
||||
return;
|
||||
}
|
||||
|
||||
if(file.size > IMAGE_MAX_SIZE){
|
||||
showToast('FILE_SIZE_OVER_ERROR', {
|
||||
type: alertTypes.warning
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (previewUrl) {
|
||||
URL.revokeObjectURL(previewUrl);
|
||||
}
|
||||
setPreviewUrl(URL.createObjectURL(file));
|
||||
|
||||
await MenuImageUpload(token, file).then(data =>{
|
||||
const message = data.data.message;
|
||||
|
||||
if (message === 'NOT_EXIT_FILE') {
|
||||
showToast('FILE_NOT_EXIT_ERROR', {
|
||||
type: alertTypes.error
|
||||
});
|
||||
|
||||
} else {
|
||||
onImageUpload(file, data.data.file_name);
|
||||
}
|
||||
}).catch(error =>{
|
||||
console.error('이미지 업로드 오류:', error);
|
||||
showToast('FILE_IMAGE_UPLOAD_ERROR', {
|
||||
type: alertTypes.error
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 파일 삭제
|
||||
const handleFileDelete = () => {
|
||||
MenuImageDelete(token, fileName).then(data => {
|
||||
if(data.result === "ERROR"){
|
||||
showToast(data.data.message, {
|
||||
type: alertTypes.error
|
||||
});
|
||||
}else{
|
||||
// input 필드 초기화
|
||||
if (document.querySelector('#fileinput')) {
|
||||
document.querySelector('#fileinput').value = '';
|
||||
}
|
||||
|
||||
if (previewUrl) {
|
||||
URL.revokeObjectURL(previewUrl);
|
||||
setPreviewUrl(null);
|
||||
}
|
||||
|
||||
onFileDelete();
|
||||
}
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
}).finally(() => {
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="form-group custom-form">
|
||||
<FileWrapper>
|
||||
{!fileName ? (
|
||||
<>
|
||||
<FileInput
|
||||
type="file"
|
||||
required
|
||||
onChange={handleFile}
|
||||
id="fileinput"
|
||||
disabled={disabledBtn}
|
||||
accept="image/png, image/jpeg, image/jpg"
|
||||
style={{ display: downloadData ? 'none' : 'block' }}
|
||||
/>
|
||||
<FileButton htmlFor="fileinput" disabled={disabled}>
|
||||
이미지 업로드
|
||||
</FileButton>
|
||||
</>
|
||||
) : (
|
||||
<PreviewContainer>
|
||||
{previewUrl && (
|
||||
<ImagePreview
|
||||
src={previewUrl}
|
||||
alt="이미지 미리보기"
|
||||
/>
|
||||
)}
|
||||
<PreviewInfo>
|
||||
{/*<FileName>*/}
|
||||
{/* {fileName}*/}
|
||||
{/*</FileName>*/}
|
||||
<DeleteButton onClick={handleFileDelete}>
|
||||
파일 삭제
|
||||
</DeleteButton>
|
||||
</PreviewInfo>
|
||||
</PreviewContainer>
|
||||
)}
|
||||
</FileWrapper>
|
||||
{!fileName && <FileNotice>* .png, .jpg 확장자의 이미지만 업로드 가능합니다.(5MB이하)</FileNotice>}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageUploadBtn;
|
||||
|
||||
const FileWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
margin-right: 20px;
|
||||
margin-left: 20px;
|
||||
gap: 10px;
|
||||
width: 50%;
|
||||
`;
|
||||
|
||||
const FileButton = styled.label`
|
||||
border-radius: 5px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: fit-content;
|
||||
font-size: 14px;
|
||||
background: #2c2c2c;
|
||||
color: #fff;
|
||||
width: 120px;
|
||||
height: 35px;
|
||||
cursor: pointer;
|
||||
|
||||
${props =>
|
||||
props.disabled &&
|
||||
css`
|
||||
background: #b8b8b8;
|
||||
cursor: not-allowed;
|
||||
`}
|
||||
`;
|
||||
|
||||
const FileInput = styled.input`
|
||||
height: 35px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 5px;
|
||||
//width: calc(100% - 120px);
|
||||
padding: 0 15px;
|
||||
line-height: 35px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
|
||||
&::file-selector-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: #cccccc;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
${props =>
|
||||
props.disabled &&
|
||||
css`
|
||||
color: #cccccc;
|
||||
background: #f6f6f6;
|
||||
`}
|
||||
`;
|
||||
|
||||
const FileNotice = styled.div`
|
||||
margin: 10px 25px 0;
|
||||
color: #cccccc;
|
||||
font-size: 10px;
|
||||
`;
|
||||
|
||||
// 새로 추가한 스타일 컴포넌트
|
||||
const PreviewContainer = styled.div`
|
||||
width: 400px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const ImagePreview = styled.img`
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
object-fit: contain;
|
||||
border-radius: 4px 4px 0 0;
|
||||
background-color: #f6f6f6;
|
||||
`;
|
||||
|
||||
const PreviewInfo = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 10px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
`;
|
||||
|
||||
const FileName = styled.div`
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 250px;
|
||||
`;
|
||||
|
||||
const DeleteButton = styled.button`
|
||||
background-color: #ff5252;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: #ff0000;
|
||||
}
|
||||
`;
|
||||
@@ -1,46 +0,0 @@
|
||||
import { TextInput, InputLabel, InputGroup, SearchBarAlert } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import { SearchBarLayout } from '../common/SearchBar';
|
||||
|
||||
import WhiteListUploadBtn from './WhiteListUploadBtn';
|
||||
|
||||
const WhiteListRegistBar = ({ handleRegistModalClose, isNullValue, resultData, setResultData }) => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
// console.log(isNullValue)
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>직접입력</InputLabel>
|
||||
<InputGroup>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="GUID 입력"
|
||||
width="300px"
|
||||
id="guid"
|
||||
onChange={e => {
|
||||
setResultData({ ...resultData, guid: e.target.value });
|
||||
}}
|
||||
onKeyDown={event => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button theme={resultData.guid ? 'search' : 'gray'} text="등록" handleClick={handleRegistModalClose} />
|
||||
</InputGroup>
|
||||
</>,
|
||||
<> {isNullValue && <SearchBarAlert>필수값을 입력해주세요</SearchBarAlert>}</>,
|
||||
<>
|
||||
<WhiteListUploadBtn />
|
||||
</>,
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchBarLayout firstColumnData={searchList} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WhiteListRegistBar;
|
||||
@@ -11,7 +11,6 @@ import OwnerChangeModal from './modal/OwnerChangeModal';
|
||||
import SearchFilter from './searchBar/SearchFilter';
|
||||
import ReportListSearchBar from './searchBar/ReportListSearchBar';
|
||||
import UserBlockSearchBar from './searchBar/UserBlockSearchBar';
|
||||
import ItemsSearchBar from './searchBar/ItemsSearchBar';
|
||||
import EventListSearchBar from './searchBar/EventListSearchBar';
|
||||
import LandAuctionSearchBar from './searchBar/LandAuctionSearchBar'
|
||||
import MailListSearchBar from './searchBar/MailListSearchBar';
|
||||
@@ -23,11 +22,10 @@ import AdminViewSearchBar from './searchBar/AdminViewSearchBar';
|
||||
import CaliumRequestSearchBar from './searchBar/CaliumRequestSearchBar';
|
||||
|
||||
import CommonSearchBar from './searchBar/CommonSearchBar';
|
||||
import useCommonSearch from './searchBar/useCommonSearch';
|
||||
import useCommonSearch from '../../hooks/useCommonSearch';
|
||||
|
||||
//etc
|
||||
import ReportListSummary from './ReportListSummary';
|
||||
import WhiteListSearchBar from './WhiteListRegistBar';
|
||||
|
||||
export {
|
||||
BoardInfoModal,
|
||||
@@ -47,8 +45,6 @@ export {
|
||||
ReportListSummary,
|
||||
UserBlockDetailModal,
|
||||
UserBlockSearchBar,
|
||||
WhiteListSearchBar,
|
||||
ItemsSearchBar,
|
||||
EventListSearchBar,
|
||||
LandAuctionSearchBar,
|
||||
LandAuctionModal,
|
||||
|
||||
473
src/components/ServiceManage/modal/MenuBannerModal.js
Normal file
473
src/components/ServiceManage/modal/MenuBannerModal.js
Normal file
@@ -0,0 +1,473 @@
|
||||
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,
|
||||
} 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 {
|
||||
battleEventHotTime,
|
||||
battleEventRoundCount,
|
||||
battleEventStatus,
|
||||
battleRepeatType,
|
||||
} from '../../../assets/data/options';
|
||||
import { BattleEventModify, BattleEventSingleRegist } from '../../../apis/Battle';
|
||||
import { battleEventStatusType } from '../../../assets/data/types';
|
||||
import { isValidDayRange } from '../../../utils/date';
|
||||
|
||||
const MenuBannerModal = ({ modalType, detailView, handleDetailView, content, setDetailData, configData, rewardData }) => {
|
||||
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(modalType === TYPE_MODIFY && content && Object.keys(content).length > 0){
|
||||
setResultData({
|
||||
group_id: content.group_id,
|
||||
event_id: content.event_id,
|
||||
event_name: content.event_name,
|
||||
repeat_type: content.repeat_type,
|
||||
config_id: content.config_id,
|
||||
reward_group_id: content.reward_group_id,
|
||||
round_count: content.round_count,
|
||||
hot_time: content.hot_time,
|
||||
round_time: content.round_time,
|
||||
status: content.status,
|
||||
event_start_dt: convertKTCDate(content.event_start_dt),
|
||||
event_end_dt: content.event_end_dt,
|
||||
event_operation_time: content.event_operation_time,
|
||||
});
|
||||
}
|
||||
}, [modalType, content]);
|
||||
|
||||
useEffect(() => {
|
||||
if(modalType === TYPE_REGISTRY && configData?.length > 0){
|
||||
setResultData(prev => ({
|
||||
...prev,
|
||||
round_count: configData[0].default_round_count
|
||||
}));
|
||||
}
|
||||
}, [modalType, configData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (checkCondition()) {
|
||||
setIsNullValue(false);
|
||||
} else {
|
||||
setIsNullValue(true);
|
||||
}
|
||||
}, [resultData]);
|
||||
|
||||
// 시작 날짜 변경 핸들러
|
||||
const handleStartDateChange = (date) => {
|
||||
if (!date) return;
|
||||
|
||||
const newDate = new Date(date);
|
||||
|
||||
if(resultData.repeat_type !== NONE && resultData.event_end_dt){
|
||||
const endDate = new Date(resultData.event_end_dt);
|
||||
const startDay = new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate());
|
||||
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
if (endDay <= startDay) {
|
||||
setAlertMsg(t('DATE_START_DIFF_END_WARNING'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setResultData(prev => ({
|
||||
...prev,
|
||||
event_start_dt: newDate
|
||||
}));
|
||||
};
|
||||
|
||||
// 시작 시간 변경 핸들러
|
||||
const handleStartTimeChange = (time) => {
|
||||
if (!time) return;
|
||||
|
||||
const newDateTime = resultData.event_start_dt
|
||||
? new Date(resultData.event_start_dt)
|
||||
: new Date();
|
||||
|
||||
newDateTime.setHours(
|
||||
time.getHours(),
|
||||
time.getMinutes(),
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
setResultData(prev => ({
|
||||
...prev,
|
||||
event_start_dt: newDateTime
|
||||
}));
|
||||
};
|
||||
|
||||
// 종료 날짜 변경 핸들러
|
||||
const handleEndDateChange = (date) => {
|
||||
if (!date || !resultData.event_start_dt) return;
|
||||
|
||||
const startDate = new Date(resultData.event_start_dt);
|
||||
const endDate = new Date(date);
|
||||
|
||||
// 일자만 비교하기 위해 년/월/일만 추출
|
||||
const startDay = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
|
||||
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
if (endDay <= startDay) {
|
||||
setAlertMsg(t('DATE_START_DIFF_END_WARNING'));
|
||||
return;
|
||||
}
|
||||
|
||||
setResultData(prev => ({
|
||||
...prev,
|
||||
event_end_dt: endDate
|
||||
}));
|
||||
};
|
||||
|
||||
const handleConfigChange = (e) => {
|
||||
const config = configData.find(data => String(data.id) === String(e.target.value));
|
||||
if (config) {
|
||||
setResultData({
|
||||
...resultData,
|
||||
config_id: config.id,
|
||||
round_time: config.round_time
|
||||
});
|
||||
} else {
|
||||
console.warn('Config not found for value:', e.target.value);
|
||||
}
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
setDetailData({});
|
||||
setResultData(initData);
|
||||
handleDetailView();
|
||||
}
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "submit":
|
||||
if (!checkCondition()) return;
|
||||
|
||||
const minAllowedTime = new Date(new Date().getTime() + 10 * 60000);
|
||||
const startDt = resultData.event_start_dt;
|
||||
const endDt = resultData.event_end_dt;
|
||||
if (modalType === TYPE_REGISTRY && startDt < minAllowedTime) {
|
||||
setAlertMsg(t('BATTLE_EVENT_MODAL_START_DT_WARNING'));
|
||||
return;
|
||||
}
|
||||
if(resultData.repeat_type !== 'NONE' && !isValidDayRange(startDt, endDt)) {
|
||||
setAlertMsg(t('DATE_START_DIFF_END_WARNING'))
|
||||
return;
|
||||
}
|
||||
|
||||
//화면에 머물면서 상태는 안바꼈을 경우가 있기에 시작시간 지났을경우 차단
|
||||
if (modalType === TYPE_REGISTRY && startDt < new Date()) {
|
||||
setAlertMsg(t('BATTLE_EVENT_MODAL_START_DT_WARNING'));
|
||||
return;
|
||||
}
|
||||
|
||||
if(resultData.round_time === 0){
|
||||
const config = configData.find(data => data.id === resultData.config_id);
|
||||
setResultData({ ...resultData, round_time: config.round_time });
|
||||
}
|
||||
|
||||
handleModalView('registConfirm');
|
||||
break;
|
||||
case "cancel":
|
||||
handleModalView('cancel');
|
||||
break;
|
||||
case "cancelConfirm":
|
||||
handleModalClose('cancel');
|
||||
handleReset();
|
||||
break;
|
||||
case "registConfirm":
|
||||
setLoading(true);
|
||||
|
||||
if(isView('modify')){
|
||||
await BattleEventModify(token, content?.id, resultData).then(data => {
|
||||
setLoading(false);
|
||||
handleModalClose('registConfirm');
|
||||
if(data.result === "SUCCESS") {
|
||||
handleModalView('registComplete');
|
||||
}else if(data.result === "ERROR_BATTLE_EVENT_TIME_OVER"){
|
||||
setAlertMsg(t('BATTLE_EVENT_MODAL_TIME_CHECK_WARNING'));
|
||||
}else{
|
||||
setAlertMsg(t('UPDATE_FAIL'));
|
||||
}
|
||||
}).catch(reason => {
|
||||
setAlertMsg(t('API_FAIL'));
|
||||
});
|
||||
}
|
||||
else{
|
||||
await BattleEventSingleRegist(token, resultData).then(data => {
|
||||
setLoading(false);
|
||||
handleModalClose('registConfirm');
|
||||
if(data.result === "SUCCESS") {
|
||||
handleModalView('registComplete');
|
||||
}else if(data.result === "ERROR_BATTLE_EVENT_TIME_OVER"){
|
||||
setAlertMsg(t('BATTLE_EVENT_MODAL_TIME_CHECK_WARNING'));
|
||||
}else{
|
||||
setAlertMsg(t('REGIST_FAIL'));
|
||||
}
|
||||
}).catch(reason => {
|
||||
setAlertMsg(t('API_FAIL'));
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "registComplete":
|
||||
handleModalClose('registComplete');
|
||||
handleReset();
|
||||
window.location.reload();
|
||||
break;
|
||||
case "warning":
|
||||
setAlertMsg('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const checkCondition = () => {
|
||||
return (
|
||||
resultData.event_start_dt !== ''
|
||||
&& resultData.group_id !== ''
|
||||
&& resultData.event_name !== ''
|
||||
&& (resultData.repeat_type === 'NONE' || (resultData.repeat_type !== 'NONE' && resultData.event_end_dt !== ''))
|
||||
);
|
||||
};
|
||||
|
||||
const isView = (label) => {
|
||||
switch (label) {
|
||||
case "modify":
|
||||
return modalType === TYPE_MODIFY && (content?.status === battleEventStatusType.stop);
|
||||
case "start_dt":
|
||||
case "repeat":
|
||||
case "registry":
|
||||
return modalType === TYPE_REGISTRY
|
||||
case "end_dt":
|
||||
case "group":
|
||||
case "name":
|
||||
case "config":
|
||||
case "reward":
|
||||
case "round":
|
||||
case "hot":
|
||||
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY &&(content?.status === battleEventStatusType.stop));
|
||||
default:
|
||||
return modalType === TYPE_MODIFY && (content?.status !== battleEventStatusType.stop);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="760px" $view={detailView}>
|
||||
<Title $align="center">{isView('registry') ? "전투시스템 이벤트 등록" : isView('modify') ? "전투시스템 이벤트 수정" : "전투시스템 이벤트 상세"}</Title>
|
||||
<MessageWrapper>
|
||||
<FormRowGroup>
|
||||
<FormLabel>그룹 ID</FormLabel>
|
||||
<FormInput
|
||||
type="text"
|
||||
disabled={!isView('group')}
|
||||
width='150px'
|
||||
value={resultData?.group_id}
|
||||
onChange={e => setResultData({ ...resultData, group_id: e.target.value })}
|
||||
/>
|
||||
<FormLabel>이벤트명</FormLabel>
|
||||
<FormInput
|
||||
type="text"
|
||||
disabled={!isView('name')}
|
||||
width='300px'
|
||||
value={resultData?.event_name}
|
||||
onChange={e => setResultData({ ...resultData, event_name: e.target.value })}
|
||||
/>
|
||||
</FormRowGroup>
|
||||
<FormRowGroup>
|
||||
<SingleDatePicker
|
||||
label="시작일자"
|
||||
disabled={!isView('start_dt')}
|
||||
dateLabel="시작 일자"
|
||||
onDateChange={handleStartDateChange}
|
||||
selectedDate={resultData?.event_start_dt}
|
||||
/>
|
||||
<SingleTimePicker
|
||||
label="시작시간"
|
||||
disabled={!isView('start_dt')}
|
||||
selectedTime={resultData?.event_start_dt}
|
||||
onTimeChange={handleStartTimeChange}
|
||||
/>
|
||||
</FormRowGroup>
|
||||
<FormRowGroup>
|
||||
<FormLabel>반복</FormLabel>
|
||||
<SelectInput value={resultData?.repeat_type} onChange={e => setResultData({ ...resultData, repeat_type: e.target.value })} disabled={!isView('repeat')} width="150px">
|
||||
{battleRepeatType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
{resultData?.repeat_type !== 'NONE' &&
|
||||
<SingleDatePicker
|
||||
label="종료일자"
|
||||
disabled={!isView('end_dt')}
|
||||
dateLabel="종료 일자"
|
||||
onDateChange={handleEndDateChange}
|
||||
selectedDate={resultData?.event_end_dt}
|
||||
/>
|
||||
}
|
||||
</FormRowGroup>
|
||||
<FormRowGroup>
|
||||
<FormLabel>라운드 시간</FormLabel>
|
||||
<SelectInput value={resultData.config_id} onChange={handleConfigChange} disabled={!isView('config')} width="200px">
|
||||
{configData && configData?.map((data, index) => (
|
||||
<option key={index} value={data.id}>
|
||||
{data.desc}({data.id})
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<FormLabel>라운드 수</FormLabel>
|
||||
<SelectInput value={resultData.round_count} onChange={e => setResultData({ ...resultData, round_count: e.target.value })} disabled={!isView('round')} width="100px">
|
||||
{battleEventRoundCount.map((data, index) => (
|
||||
<option key={index} value={data}>
|
||||
{data}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</FormRowGroup>
|
||||
<FormRowGroup>
|
||||
<FormLabel>배정 포드</FormLabel>
|
||||
<SelectInput value={resultData.reward_group_id} onChange={e => setResultData({ ...resultData, reward_group_id: e.target.value })} disabled={!isView('reward')} width="200px">
|
||||
{rewardData && rewardData?.map((data, index) => (
|
||||
<option key={index} value={data.group_id}>
|
||||
{data.desc}({data.group_id})
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<FormLabel>핫타임</FormLabel>
|
||||
<SelectInput value={resultData.hot_time} onChange={e => setResultData({ ...resultData, hot_time: e.target.value })} disabled={!isView('hot')} width="100px">
|
||||
{battleEventHotTime.map((data, index) => (
|
||||
<option key={index} value={data}>
|
||||
{data}배
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</FormRowGroup>
|
||||
|
||||
{!isView() && isNullValue && <SearchBarAlert $marginTop="25px" $align="right">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
|
||||
</MessageWrapper>
|
||||
|
||||
<BtnWrapper $gap="10px" $marginTop="10px">
|
||||
<FormStatusBar>
|
||||
<FormStatusLabel>
|
||||
현재상태: {battleEventStatus.find(data => data.value === content?.status)?.name || "등록"}
|
||||
</FormStatusLabel>
|
||||
<FormStatusWarning>
|
||||
{isView('registry') ? '' : t('BATTLE_EVENT_MODAL_STATUS_WARNING')}
|
||||
</FormStatusWarning>
|
||||
</FormStatusBar>
|
||||
<FormButtonContainer $gap="5px">
|
||||
{isView() ?
|
||||
<Button
|
||||
text="확인"
|
||||
name="확인버튼"
|
||||
theme="line"
|
||||
handleClick={() => handleReset()}
|
||||
/>
|
||||
:
|
||||
<>
|
||||
<Button text="취소" theme="line" handleClick={() => handleSubmit('cancel')} />
|
||||
<Button
|
||||
type="submit"
|
||||
text={isView('modify') ? "수정" : "등록"}
|
||||
name="등록버튼"
|
||||
theme={
|
||||
checkCondition()
|
||||
? 'primary'
|
||||
: 'disable'
|
||||
}
|
||||
handleClick={() => handleSubmit('submit')}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</FormButtonContainer>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 확인 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.registConfirmModal}
|
||||
modalText={isView('modify') ? t('BATTLE_EVENT_UPDATE_CONFIRM') : t('BATTLE_EVENT_REGIST_CONFIRM')}
|
||||
handleSubmit={() => handleSubmit('registConfirm')}
|
||||
handleCancel={() => handleModalClose('registConfirm')}
|
||||
/>
|
||||
{/* 완료 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={modalState.registCompleteModal}
|
||||
modalText={isView('modify') ? t('UPDATE_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/>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const initData = {
|
||||
group_id: '',
|
||||
event_name: '',
|
||||
repeat_type: 'NONE',
|
||||
config_id: 1,
|
||||
round_time: 0,
|
||||
reward_group_id: 1,
|
||||
round_count: 1,
|
||||
hot_time: 1,
|
||||
event_start_dt: '',
|
||||
event_end_dt: ''
|
||||
}
|
||||
|
||||
export default MenuBannerModal;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { TextInput, InputLabel, SelectInput, InputGroup } from '../../../styles/
|
||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
||||
import { Fragment } from 'react';
|
||||
import { getOptionsArray } from '../../../utils';
|
||||
import { PageSkeleton } from '../../Skeleton/SearchSkeleton';
|
||||
|
||||
const renderSearchField = (field, searchParams, onSearch) => {
|
||||
const { type, id, label, placeholder, width, optionsRef } = field;
|
||||
@@ -10,13 +11,20 @@ const renderSearchField = (field, searchParams, onSearch) => {
|
||||
case 'text':
|
||||
return (
|
||||
<>
|
||||
{label && <InputLabel>{label}</InputLabel>}
|
||||
{label && label !== 'undefined' && <InputLabel $require={field.required}>{label}</InputLabel>}
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={placeholder || ''}
|
||||
value={searchParams[id] || ''}
|
||||
width={width || '100%'}
|
||||
onChange={e => onSearch({ [id]: e.target.value }, false)}
|
||||
required={field.required}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
onSearch(searchParams, true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -24,7 +32,7 @@ const renderSearchField = (field, searchParams, onSearch) => {
|
||||
case 'select':
|
||||
return (
|
||||
<>
|
||||
{label && <InputLabel>{label}</InputLabel>}
|
||||
{label && label !== 'undefined' && <InputLabel $require={field.required}>{label}</InputLabel>}
|
||||
<SelectInput
|
||||
value={searchParams[id] || ''}
|
||||
onChange={e => onSearch({ [id]: e.target.value }, false)}
|
||||
@@ -41,7 +49,7 @@ const renderSearchField = (field, searchParams, onSearch) => {
|
||||
case 'period':
|
||||
return (
|
||||
<>
|
||||
{label && <InputLabel>{label}</InputLabel>}
|
||||
{label && label !== 'undefined' && <InputLabel $require={field.required}>{label}</InputLabel>}
|
||||
<SearchPeriod
|
||||
startDate={searchParams[field.startDateId]}
|
||||
handleStartDate={date => onSearch({ [field.startDateId]: date }, false)}
|
||||
@@ -76,6 +84,12 @@ const renderSearchField = (field, searchParams, onSearch) => {
|
||||
value={searchParams[field.inputId] || ''}
|
||||
width={field.width || '100%'}
|
||||
onChange={e => onSearch({ [field.inputId]: e.target.value }, false)}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
onSearch(searchParams, true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</InputGroup>
|
||||
);
|
||||
@@ -87,7 +101,7 @@ const renderSearchField = (field, searchParams, onSearch) => {
|
||||
|
||||
const CommonSearchBar = ({ config, searchParams, onSearch, onReset, customProps }) => {
|
||||
if (!config || !config.searchFields) {
|
||||
return <div>Loading search configuration...</div>;
|
||||
return <PageSkeleton />;
|
||||
}
|
||||
|
||||
const handleSubmit = event => {
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
|
||||
import Button from '../../common/button/Button';
|
||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
||||
|
||||
const ItemsSearchBar = ({ handleSearch, setResultData }) => {
|
||||
const [searchData, setSearchData] = useState({
|
||||
searchType: 'GUID',
|
||||
data: '',
|
||||
status: 'ALL',
|
||||
restore: 'ALL',
|
||||
sendDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
|
||||
const searchType = [
|
||||
{ value: 'GUID', name: 'GUID' },
|
||||
{ value: 'NAME', name: '닉네임' }
|
||||
];
|
||||
|
||||
const status = [
|
||||
{ value: 'ALL', name: '상태' },
|
||||
{ value: 'ACTIVE', name: '활성' },
|
||||
{ value: 'DEACTIVE', name: '비활성' },
|
||||
];
|
||||
|
||||
const restore = [
|
||||
{ value: 'ALL', name: '복구' },
|
||||
{ value: 'POSSIBLE', name: '가능' },
|
||||
{ value: 'IMPOSSIBLE', name: '불가능' },
|
||||
];
|
||||
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
handleSearch(
|
||||
searchData.searchType ? searchData.searchType : 'GUID',
|
||||
searchData.data,
|
||||
searchData.status ? searchData.status : 'ALL',
|
||||
searchData.restore ? searchData.restore : 'ALL',
|
||||
searchData.sendDate ? searchData.sendDate : '',
|
||||
searchData.endDate ? searchData.endDate : new Date(),
|
||||
);
|
||||
|
||||
setResultData(searchData);
|
||||
};
|
||||
|
||||
// 초기화 버튼
|
||||
const handleReset = () => {
|
||||
setSearchData({
|
||||
searchType: 'GUID',
|
||||
data: '',
|
||||
status: 'ALL',
|
||||
restore: 'ALL',
|
||||
sendDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
handleSearch('GUID', '', 'ALL', 'ALL', '', '');
|
||||
setResultData('GUID', '', 'ALL', 'ALL', '', '');
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
// console.log(searchData);
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchData.searchType} onChange={e => setSearchData({ ...searchData, searchType: e.target.value })}>
|
||||
{searchType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchData.searchType === 'GUID' ? 'GUID 입력' : '닉네임 입력'}
|
||||
value={searchData.data}
|
||||
width="600px"
|
||||
onChange={e => setSearchData({ ...searchData, data: e.target.value })}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<SelectInput value={searchData.status} onChange={e => setSearchData({ ...searchData, status: e.target.value })}>
|
||||
{status.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<SelectInput value={searchData.restore} onChange={e => setSearchData({ ...searchData, restore: e.target.value })}>
|
||||
{restore.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>생성 날짜</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchData.sendDate}
|
||||
handleStartDate={data => {
|
||||
setSearchData({ ...searchData, sendDate: data });
|
||||
}}
|
||||
endDate={searchData.endDate}
|
||||
handleEndDate={data => setSearchData({ ...searchData, endDate: data })}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
</>,
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} onReset={handleReset} handleSubmit={handleSubmit} />;
|
||||
};
|
||||
|
||||
export default ItemsSearchBar;
|
||||
@@ -55,6 +55,15 @@ const SearchFilter = ({ value = [], onChange }) => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
const TextInputWithHelp = ({ helpText, ...props }) => {
|
||||
return (
|
||||
<TextInputWrapper>
|
||||
<TextInput {...props} />
|
||||
{helpText && <HelpText>{helpText}</HelpText>}
|
||||
</TextInputWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<FilterWrapper>
|
||||
<FilterToggle onClick={toggleFilters}>
|
||||
@@ -81,6 +90,7 @@ const SearchFilter = ({ value = [], onChange }) => {
|
||||
<InputLabel>속성 이름</InputLabel>
|
||||
<TextInput
|
||||
type="text"
|
||||
width="150px"
|
||||
placeholder="속성 이름 입력"
|
||||
value={section.field_name}
|
||||
onChange={(e) => handleInputChange(index, 'field_name', e.target.value)}
|
||||
@@ -99,11 +109,13 @@ const SearchFilter = ({ value = [], onChange }) => {
|
||||
</SelectInput>
|
||||
|
||||
<InputLabel>속성 값</InputLabel>
|
||||
<TextInput
|
||||
<TextInputWithHelp
|
||||
type="text"
|
||||
width="300px"
|
||||
placeholder="속성 값 입력"
|
||||
value={section.value}
|
||||
onChange={(e) => handleInputChange(index, 'value', e.target.value)}
|
||||
helpText="여러개의 값 예시> (1011|1012|1013)"
|
||||
/>
|
||||
|
||||
<AddButton onClick={() => handleAddFilter(index)}>추가</AddButton>
|
||||
@@ -125,6 +137,23 @@ const SearchFilter = ({ value = [], onChange }) => {
|
||||
|
||||
export default SearchFilter;
|
||||
|
||||
const TextInputWrapper = styled.div`
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
const HelpText = styled.div`
|
||||
font-size: 11px;
|
||||
color: #888;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding-top: 2px;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const FilterWrapper = styled.div`
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
@@ -173,7 +202,9 @@ const FilterValue = styled.span`
|
||||
color: #333;
|
||||
`;
|
||||
|
||||
const RemoveButton = styled.button`
|
||||
const RemoveButton = styled.button.attrs({
|
||||
type: 'button'
|
||||
})`
|
||||
background: none;
|
||||
border: none;
|
||||
color: #888;
|
||||
@@ -215,7 +246,6 @@ const FilterInputSection = styled.div`
|
||||
align-items: center;
|
||||
|
||||
${TextInput} {
|
||||
width: 160px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
17
src/components/Skeleton/SearchSkeleton.js
Normal file
17
src/components/Skeleton/SearchSkeleton.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { BtnWrapper, SearchbarStyle, SearchRow, Skeleton } from '../../styles/Components';
|
||||
|
||||
export const PageSkeleton = () => {
|
||||
return (
|
||||
<SearchbarStyle direction="column">
|
||||
<SearchRow>
|
||||
<Skeleton width="80%" height="35px" />
|
||||
</SearchRow>
|
||||
<SearchRow>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Skeleton width="100px" height="35px" />
|
||||
<Skeleton width="35px" height="35px" />
|
||||
</BtnWrapper>
|
||||
</SearchRow>
|
||||
</SearchbarStyle>
|
||||
)
|
||||
}
|
||||
526
src/components/common/Custom/CaliForm.js
Normal file
526
src/components/common/Custom/CaliForm.js
Normal file
@@ -0,0 +1,526 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getOptionsArray } from '../../../utils';
|
||||
import {
|
||||
SelectInput,
|
||||
SearchBarAlert
|
||||
} from '../../../styles/Components';
|
||||
import {
|
||||
FormInput, FormInputSuffix, FormInputSuffixWrapper,
|
||||
FormLabel,
|
||||
FormRowGroup,
|
||||
FormStatusBar,
|
||||
FormStatusLabel,
|
||||
FormStatusWarning,
|
||||
} from '../../../styles/ModuleComponents';
|
||||
import { CheckBox, SingleDatePicker, SingleTimePicker } from '../../common';
|
||||
import Button from '../../common/button/Button';
|
||||
import styled from 'styled-components';
|
||||
import ImageUploadBtn from '../../ServiceManage/ImageUploadBtn';
|
||||
|
||||
const CaliForm = ({
|
||||
config, // 폼 설정 JSON
|
||||
mode, // 'create', 'update', 'view' 중 하나
|
||||
initialData, // 초기 데이터
|
||||
externalData, // 외부 데이터(옵션 등)
|
||||
onSubmit, // 제출 핸들러
|
||||
onCancel, // 취소 핸들러
|
||||
className, // 추가 CSS 클래스
|
||||
onFieldValidation, // 필드 유효성 검사 콜백
|
||||
formRef // 폼 ref
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [formData, setFormData] = useState({ ...(config?.initData || {}), ...(initialData || {}) });
|
||||
const [errors, setErrors] = useState({});
|
||||
const [isFormValid, setIsFormValid] = useState(false);
|
||||
|
||||
// 필드 변경 핸들러
|
||||
const handleFieldChange = (fieldId, value) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[fieldId]: value
|
||||
}));
|
||||
};
|
||||
|
||||
// 날짜 변경 핸들러
|
||||
const handleDateChange = (fieldId, date) => {
|
||||
if (!date) return;
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[fieldId]: date
|
||||
}));
|
||||
};
|
||||
|
||||
// 시간 변경 핸들러
|
||||
const handleTimeChange = (fieldId, time) => {
|
||||
if (!time) return;
|
||||
|
||||
const newDateTime = formData[fieldId] ? new Date(formData[fieldId]) : new Date();
|
||||
newDateTime.setHours(time.getHours(), time.getMinutes(), 0, 0);
|
||||
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[fieldId]: newDateTime
|
||||
}));
|
||||
};
|
||||
|
||||
// 폼 유효성 검사
|
||||
useEffect(() => {
|
||||
const validateForm = () => {
|
||||
const newErrors = {};
|
||||
let isValid = true;
|
||||
|
||||
if (!config) return false;
|
||||
|
||||
// 필수 필드 검사
|
||||
const requiredFields = config.fields
|
||||
.filter(f =>
|
||||
f.visibleOn.includes(mode) &&
|
||||
f.validations?.includes("required")
|
||||
)
|
||||
.map(f => f.id);
|
||||
|
||||
requiredFields.forEach(fieldId => {
|
||||
if (!formData[fieldId] && formData[fieldId] !== 0) {
|
||||
newErrors[fieldId] = t('REQUIRED_FIELD');
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 조건부 유효성 검사
|
||||
if (config.validations && config.validations[mode]) {
|
||||
for (const validation of config.validations[mode]) {
|
||||
const conditionResult = evaluateCondition(validation.condition, {
|
||||
...formData,
|
||||
current_time: new Date().getTime()
|
||||
});
|
||||
|
||||
if (conditionResult) {
|
||||
// 전체 폼 검증 오류
|
||||
newErrors._form = t(validation.message);
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
setIsFormValid(isValid);
|
||||
|
||||
if (onFieldValidation) {
|
||||
onFieldValidation(isValid, newErrors);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
};
|
||||
|
||||
validateForm();
|
||||
}, [config, formData, mode, t, onFieldValidation]);
|
||||
|
||||
// 간단한 조건식 평가 함수
|
||||
const evaluateCondition = (conditionStr, context) => {
|
||||
try {
|
||||
const fn = new Function(...Object.keys(context), `return ${conditionStr}`);
|
||||
return fn(...Object.values(context));
|
||||
} catch (e) {
|
||||
console.error('Error evaluating condition:', e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 필드 렌더링
|
||||
const renderField = (field) => {
|
||||
const isEditable = field.editableOn.includes(mode);
|
||||
const value = formData[field.id] !== undefined ? formData[field.id] : '';
|
||||
const hasError = errors[field.id];
|
||||
|
||||
switch (field.type) {
|
||||
case 'text':
|
||||
return (
|
||||
<div className="form-field">
|
||||
<FormInput
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={e => handleFieldChange(field.id, e.target.value)}
|
||||
disabled={!isEditable}
|
||||
width={field.width}
|
||||
className={hasError ? 'error' : ''}
|
||||
/>
|
||||
{hasError && <div className="field-error">{hasError}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'number':
|
||||
return (
|
||||
<div className="form-field">
|
||||
<FormInput
|
||||
type="number"
|
||||
value={value}
|
||||
onChange={e => handleFieldChange(field.id, Number(e.target.value))}
|
||||
disabled={!isEditable}
|
||||
width={field.width}
|
||||
min={field.min}
|
||||
max={field.max}
|
||||
step={field.step || 1}
|
||||
className={hasError ? 'error' : ''}
|
||||
/>
|
||||
{hasError && <div className="field-error">{hasError}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'select':
|
||||
let options = [];
|
||||
|
||||
if (field.optionsKey) {
|
||||
// 옵션 설정에서 가져오기
|
||||
options = getOptionsArray(field.optionsKey);
|
||||
} else if (field.dataSource && externalData) {
|
||||
// 외부 데이터 소스 사용
|
||||
const dataSource = externalData[field.dataSource] || [];
|
||||
|
||||
options = dataSource.map(item => ({
|
||||
value: item[field.valueField],
|
||||
label: field.displayFormat
|
||||
? field.displayFormat.replace('{value}', item[field.valueField])
|
||||
.replace('{display}', item[field.displayField])
|
||||
: `${item[field.displayField]}(${item[field.valueField]})`
|
||||
}));
|
||||
} else if (field.options) {
|
||||
options = field.options;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="form-field">
|
||||
<SelectInput
|
||||
value={value}
|
||||
onChange={e => handleFieldChange(field.id, e.target.value)}
|
||||
disabled={!isEditable}
|
||||
width={field.width}
|
||||
className={hasError ? 'error' : ''}
|
||||
>
|
||||
{options.map((option, index) => (
|
||||
<option key={index} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
{hasError && <div className="field-error">{hasError}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'datePicker':
|
||||
return (
|
||||
<div className="form-field">
|
||||
<SingleDatePicker
|
||||
label={field.label}
|
||||
disabled={!isEditable}
|
||||
dateLabel={field.dateLabel}
|
||||
onDateChange={date => handleDateChange(field.id, date)}
|
||||
selectedDate={value}
|
||||
minDate={field.minDate}
|
||||
maxDate={field.maxDate}
|
||||
className={hasError ? 'error' : ''}
|
||||
/>
|
||||
{hasError && <div className="field-error">{hasError}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'timePicker':
|
||||
return (
|
||||
<div className="form-field">
|
||||
<SingleTimePicker
|
||||
label={field.label}
|
||||
disabled={!isEditable}
|
||||
selectedTime={value}
|
||||
onTimeChange={time => handleTimeChange(field.id, time)}
|
||||
className={hasError ? 'error' : ''}
|
||||
/>
|
||||
{hasError && <div className="field-error">{hasError}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'status':
|
||||
let statusText = "";
|
||||
if (field.optionsKey && formData[field.statusField]) {
|
||||
const statusOptions = getOptionsArray(field.optionsKey);
|
||||
const statusItem = statusOptions.find(item => item.value === formData[field.statusField]);
|
||||
statusText = statusItem ? statusItem.name : "등록";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="form-field">
|
||||
<FormStatusBar>
|
||||
<FormStatusLabel>
|
||||
{field.label}: {statusText}
|
||||
</FormStatusLabel>
|
||||
{mode === 'update' && field.warningMessage && (
|
||||
<FormStatusWarning>
|
||||
{t(field.warningMessage)}
|
||||
</FormStatusWarning>
|
||||
)}
|
||||
</FormStatusBar>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'dateTimeRange':
|
||||
return (
|
||||
<div className="form-field">
|
||||
<div className="date-time-range">
|
||||
<SingleDatePicker
|
||||
label={field.startDateLabel}
|
||||
disabled={!isEditable}
|
||||
dateLabel={field.startDateLabel}
|
||||
onDateChange={date => handleDateChange(field.startDateField, date)}
|
||||
selectedDate={formData[field.startDateField]}
|
||||
/>
|
||||
<SingleTimePicker
|
||||
disabled={!isEditable}
|
||||
selectedTime={formData[field.startDateField]}
|
||||
onTimeChange={time => handleTimeChange(field.startDateField, time)}
|
||||
/>
|
||||
<SingleDatePicker
|
||||
label={field.endDateLabel}
|
||||
disabled={!isEditable}
|
||||
dateLabel={field.endDateLabel}
|
||||
onDateChange={date => handleDateChange(field.endDateField, date)}
|
||||
selectedDate={formData[field.endDateField]}
|
||||
/>
|
||||
<SingleTimePicker
|
||||
disabled={!isEditable}
|
||||
selectedTime={formData[field.endDateField]}
|
||||
onTimeChange={time => handleTimeChange(field.endDateField, time)}
|
||||
/>
|
||||
</div>
|
||||
{hasError && <div className="field-error">{hasError}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'imageUpload':
|
||||
const imageLanguage = field.language;
|
||||
const imageList = formData.image_list || [];
|
||||
const imageData = imageList.find(img => img.language === imageLanguage) || { content: '' };
|
||||
|
||||
return (
|
||||
<div className="form-field">
|
||||
<LanguageWrapper>
|
||||
<LanguageLabel>{imageLanguage}</LanguageLabel>
|
||||
<ImageUploadBtn
|
||||
onImageUpload={(file, fileName) => {
|
||||
const updatedImageList = [...imageList];
|
||||
const index = updatedImageList.findIndex(img => img.language === imageLanguage);
|
||||
|
||||
if (index !== -1) {
|
||||
updatedImageList[index] = {
|
||||
...updatedImageList[index],
|
||||
content: fileName
|
||||
};
|
||||
} else {
|
||||
updatedImageList.push({
|
||||
language: imageLanguage,
|
||||
content: fileName
|
||||
});
|
||||
}
|
||||
|
||||
handleFieldChange('image_list', updatedImageList);
|
||||
}}
|
||||
onFileDelete={() => {
|
||||
const updatedImageList = [...imageList];
|
||||
const index = updatedImageList.findIndex(img => img.language === imageLanguage);
|
||||
|
||||
if (index !== -1) {
|
||||
updatedImageList[index] = {
|
||||
...updatedImageList[index],
|
||||
content: ''
|
||||
};
|
||||
|
||||
handleFieldChange('image_list', updatedImageList);
|
||||
}
|
||||
}}
|
||||
fileName={imageData.content}
|
||||
disabled={!isEditable}
|
||||
/>
|
||||
</LanguageWrapper>
|
||||
{hasError && <div className="field-error">{hasError}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'checkbox':
|
||||
return (
|
||||
<div className="form-field">
|
||||
<CheckBox
|
||||
label={field.label}
|
||||
id={field.id}
|
||||
checked={formData[field.id] || false}
|
||||
setData={e => handleFieldChange(field.id, e.target.checked)}
|
||||
disabled={!isEditable}
|
||||
/>
|
||||
{hasError && <div className="field-error">{hasError}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'textWithSuffix':
|
||||
const linkLanguage = field.suffix;
|
||||
const linkList = formData.link_list || [];
|
||||
const linkData = linkList.find(link => link.language === linkLanguage) || { content: '' };
|
||||
|
||||
return (
|
||||
<div className="form-field">
|
||||
{field.label && <FormLabel>{field.label}</FormLabel>}
|
||||
<FormInputSuffixWrapper>
|
||||
<FormInput
|
||||
type="text"
|
||||
value={linkData.content}
|
||||
onChange={e => {
|
||||
const updatedLinkList = [...linkList];
|
||||
const index = updatedLinkList.findIndex(link => link.language === linkLanguage);
|
||||
|
||||
if (index !== -1) {
|
||||
updatedLinkList[index] = {
|
||||
...updatedLinkList[index],
|
||||
content: e.target.value
|
||||
};
|
||||
} else {
|
||||
updatedLinkList.push({
|
||||
language: linkLanguage,
|
||||
content: e.target.value
|
||||
});
|
||||
}
|
||||
|
||||
handleFieldChange('link_list', updatedLinkList);
|
||||
}}
|
||||
disabled={!isEditable}
|
||||
width={field.width}
|
||||
suffix="true"
|
||||
/>
|
||||
<FormInputSuffix>{linkLanguage}</FormInputSuffix>
|
||||
</FormInputSuffixWrapper>
|
||||
{hasError && <div className="field-error">{hasError}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 조건부 렌더링을 위한 필드 필터링
|
||||
const getVisibleFields = () => {
|
||||
if (!config) return [];
|
||||
|
||||
return config.fields.filter(field => {
|
||||
if (!field.visibleOn.includes(mode)) return false;
|
||||
|
||||
// 조건부 표시 필드 처리
|
||||
if (field.conditional) {
|
||||
const { field: condField, operator, value } = field.conditional;
|
||||
|
||||
if (operator === "==" && formData[condField] !== value) return false;
|
||||
if (operator === "!=" && formData[condField] === value) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
// 그리드 기반 필드 렌더링
|
||||
const renderGridFields = () => {
|
||||
if (!config) return null;
|
||||
|
||||
const visibleFields = getVisibleFields();
|
||||
const { rows, columns } = config.grid;
|
||||
|
||||
// 그리드 레이아웃 생성
|
||||
return (
|
||||
<div className="form-grid" style={{ display: 'grid', gridTemplateColumns: `repeat(${columns}, 1fr)`, gap: '10px' }}>
|
||||
{visibleFields.map((field) => {
|
||||
const { row, col, width } = field.position;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={field.id}
|
||||
className="form-cell"
|
||||
style={{
|
||||
gridRow: row + 1,
|
||||
gridColumn: `${col + 1} / span ${width}`,
|
||||
padding: '5px'
|
||||
}}
|
||||
>
|
||||
<FormRowGroup>
|
||||
<FormLabel>{field.label}{field.validations?.includes("required") && <span className="required">*</span>}</FormLabel>
|
||||
{renderField(field)}
|
||||
</FormRowGroup>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 버튼 렌더링
|
||||
const renderButtons = () => {
|
||||
if (!config || !config.actions || !config.actions[mode]) return null;
|
||||
|
||||
return (
|
||||
<div className="form-actions">
|
||||
{config.actions[mode].map(action => (
|
||||
<Button
|
||||
key={action.id}
|
||||
text={action.label}
|
||||
theme={action.theme}
|
||||
handleClick={() => {
|
||||
if (action.action === 'submit') {
|
||||
if (isFormValid) {
|
||||
onSubmit(formData);
|
||||
}
|
||||
} else if (action.action === 'close' || action.action === 'cancel') {
|
||||
onCancel();
|
||||
}
|
||||
}}
|
||||
disabled={action.action === 'submit' && !isFormValid}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (!config) return <div>로딩 중...</div>;
|
||||
|
||||
return (
|
||||
<div className={`json-config-form ${className || ''}`} ref={formRef}>
|
||||
<div className="form-content">
|
||||
{renderGridFields()}
|
||||
|
||||
{errors._form && (
|
||||
<SearchBarAlert $marginTop="15px" $align="right">
|
||||
{errors._form}
|
||||
</SearchBarAlert>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-footer">
|
||||
{renderButtons()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CaliForm;
|
||||
|
||||
const LanguageWrapper = styled.div`
|
||||
width: ${props => props.width || '100%'};
|
||||
//margin-bottom: 20px;
|
||||
padding-bottom: 20px;
|
||||
padding-left: 90px;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const LanguageLabel = styled.h4`
|
||||
color: #444;
|
||||
margin: 0 0 10px 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
`;
|
||||
@@ -3,14 +3,16 @@ import { StatusLabel } from '../../../styles/ModuleComponents';
|
||||
import { Button, CheckBox } from '../index';
|
||||
import { convertKTC, getOptionsArray } from '../../../utils';
|
||||
import { styled } from 'styled-components';
|
||||
import { TableSkeleton } from '../../Skeleton/TableSkeleton';
|
||||
|
||||
const CaliTable = ({
|
||||
columns,
|
||||
data,
|
||||
selectedRows = [],
|
||||
onSelectRow,
|
||||
isRowSelected,
|
||||
onAction,
|
||||
refProp
|
||||
refProp,
|
||||
loading = false
|
||||
}) => {
|
||||
|
||||
const renderCell = (column, item) => {
|
||||
@@ -51,7 +53,7 @@ const CaliTable = ({
|
||||
name={column.name || 'select'}
|
||||
id={item.id}
|
||||
setData={(e) => onSelectRow(e, item)}
|
||||
checked={selectedRows.some(row => row.id === item.id)}
|
||||
checked={isRowSelected(item.id)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -74,6 +76,7 @@ const CaliTable = ({
|
||||
};
|
||||
|
||||
return (
|
||||
loading ? <TableSkeleton count={15}/> :
|
||||
<TableWrapper>
|
||||
<TableStyle ref={refProp}>
|
||||
<caption></caption>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { TextInput, SelectInput, SearchBarAlert, BtnWrapper } from '../../../styles/Components';
|
||||
import {
|
||||
BtnWrapper,
|
||||
SearchRow,
|
||||
SearchbarStyle, SearchItem,
|
||||
} from '../../../styles/Components';
|
||||
import Button from '../button/Button';
|
||||
|
||||
const SearchBarLayout = ({ firstColumnData, secondColumnData, filter, direction, onReset, handleSubmit, isSearch = true }) => {
|
||||
@@ -36,42 +39,4 @@ const SearchBarLayout = ({ firstColumnData, secondColumnData, filter, direction,
|
||||
|
||||
export default SearchBarLayout;
|
||||
|
||||
const SearchbarStyle = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
font-size: 14px;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ddd;
|
||||
margin: 0 0 40px;
|
||||
flex-flow: ${props => props.direction};
|
||||
gap: ${props => (props.direction === 'column' ? '20px' : '20px 0')};
|
||||
`;
|
||||
|
||||
const SearchItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-right: 50px;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
|
||||
const SearchRow = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px 0;
|
||||
|
||||
&:last-child {
|
||||
border-top: 1px solid #e0e0e0;
|
||||
padding-top: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -13,7 +13,10 @@ const TableHeader = ({
|
||||
handlePageSize,
|
||||
selectedRows = [],
|
||||
onAction,
|
||||
navigate
|
||||
navigate,
|
||||
pagination,
|
||||
goToNextPage,
|
||||
goToPrevPage
|
||||
}) => {
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const { t } = useTranslation();
|
||||
@@ -48,7 +51,7 @@ const TableHeader = ({
|
||||
);
|
||||
}
|
||||
|
||||
const buttonTheme = button.disableWhen === 'noSelection' && selectedRows.length === 0
|
||||
const buttonTheme = (button.disableWhen === 'noSelection' && selectedRows.length === 0) || button.disableWhen === 'disable'
|
||||
? 'disable'
|
||||
: button.theme;
|
||||
|
||||
@@ -58,6 +61,7 @@ const TableHeader = ({
|
||||
theme={buttonTheme}
|
||||
text={button.text}
|
||||
handleClick={(e) => handleButtonClick(button, e)}
|
||||
disabled={button.disableWhen === 'disable'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -71,6 +75,9 @@ const TableHeader = ({
|
||||
orderType={config.orderType}
|
||||
pageType={config.pageType}
|
||||
countType={config.countType}
|
||||
pagination={pagination}
|
||||
goToNextPage={goToNextPage}
|
||||
goToPrevPage={goToPrevPage}
|
||||
>
|
||||
{config.buttons.map(renderButton)}
|
||||
</ViewTableInfo>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
HeaderPaginationContainer,
|
||||
ListCount,
|
||||
ListOption,
|
||||
SelectInput,
|
||||
@@ -6,6 +7,8 @@ import {
|
||||
} from '../../../styles/Components';
|
||||
import { ORDER_OPTIONS, PAGE_SIZE_OPTIONS, ViewTitleCountType } from '../../../assets/data';
|
||||
import { TitleItem, TitleItemLabel, TitleItemValue } from '../../../styles/ModuleComponents';
|
||||
import { DynamoPagination } from '../index';
|
||||
import React from 'react';
|
||||
|
||||
const ViewTableInfo = ({
|
||||
children,
|
||||
@@ -15,7 +18,10 @@ const ViewTableInfo = ({
|
||||
handleOrderBy,
|
||||
pageType = 'default',
|
||||
handlePageSize,
|
||||
countType = ViewTitleCountType.total
|
||||
countType = ViewTitleCountType.total,
|
||||
pagination,
|
||||
goToNextPage,
|
||||
goToPrevPage
|
||||
}) => {
|
||||
return (
|
||||
<TableInfo>
|
||||
@@ -26,9 +32,18 @@ const ViewTableInfo = ({
|
||||
COUNT_TYPE_RENDERERS[ViewTitleCountType.total](total, total_all)}
|
||||
</ListCount>
|
||||
}
|
||||
{pagination !== undefined && goToNextPage !== undefined && goToPrevPage !== undefined &&
|
||||
<HeaderPaginationContainer>
|
||||
<DynamoPagination
|
||||
pagination={pagination}
|
||||
onNextPage={goToNextPage}
|
||||
onPrevPage={goToPrevPage}
|
||||
/>
|
||||
</HeaderPaginationContainer>
|
||||
}
|
||||
<ListOption>
|
||||
<OrderBySelect orderType={orderType} handleOrderBy={handleOrderBy} />
|
||||
<PageSelect pageType={pageType} handlePageSize={handlePageSize} />
|
||||
{handleOrderBy !== undefined && <OrderBySelect orderType={orderType} handleOrderBy={handleOrderBy} />}
|
||||
{handlePageSize !== undefined && <PageSelect pageType={pageType} handlePageSize={handlePageSize} />}
|
||||
{children}
|
||||
</ListOption>
|
||||
</TableInfo>
|
||||
|
||||
@@ -3,7 +3,7 @@ import Button from '../button/Button';
|
||||
import Modal from './Modal';
|
||||
import { modalTypes } from '../../../assets/data';
|
||||
|
||||
const DynamicModal = ({modalType, view, handleSubmit, handleCancel, modalText, children}) => {
|
||||
const DynamicModal = ({modalType, view, handleSubmit, handleCancel, modalText, children, ChildView}) => {
|
||||
if (!view) return null;
|
||||
|
||||
const OkButton = ({handleClick}) => {
|
||||
@@ -58,7 +58,7 @@ const DynamicModal = ({modalType, view, handleSubmit, handleCancel, modalText, c
|
||||
<ButtonClose onClick={handleCancel} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
{children && children}
|
||||
{ChildView && <ChildView />}
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleCancel} />
|
||||
|
||||
@@ -16,7 +16,8 @@ export const AlertProvider = ({ children }) => {
|
||||
type: alertTypes.info,
|
||||
onConfirm: null,
|
||||
onCancel: null,
|
||||
children: null
|
||||
children: null,
|
||||
ChildView: null
|
||||
});
|
||||
|
||||
// 토스트 알림 상태 (여러 개를 관리하기 위해 배열 사용)
|
||||
@@ -62,7 +63,8 @@ export const AlertProvider = ({ children }) => {
|
||||
onConfirm = null,
|
||||
onCancel = null,
|
||||
translateKey = true,
|
||||
children = null
|
||||
children = null,
|
||||
ChildView = null
|
||||
} = options;
|
||||
|
||||
setModalState({
|
||||
@@ -71,7 +73,8 @@ export const AlertProvider = ({ children }) => {
|
||||
type,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
children
|
||||
children,
|
||||
ChildView
|
||||
});
|
||||
}, [t]);
|
||||
|
||||
@@ -141,6 +144,7 @@ export const AlertProvider = ({ children }) => {
|
||||
handleSubmit={handleConfirm}
|
||||
handleCancel={modalState.type === alertTypes.confirm || modalState.type === alertTypes.confirmChildren ? handleCancel : null}
|
||||
children={modalState.children}
|
||||
ChildView={modalState.ChildView}
|
||||
/>
|
||||
</AlertContext.Provider>
|
||||
);
|
||||
|
||||
227
src/hooks/useCommonSearch.js
Normal file
227
src/hooks/useCommonSearch.js
Normal file
@@ -0,0 +1,227 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { loadConfig } from '../utils';
|
||||
import { useAlert } from '../context/AlertProvider';
|
||||
import { alertTypes } from '../assets/data/types';
|
||||
import * as APIConfigs from '../assets/data/apis'
|
||||
import { callAPI } from '../utils/apiService';
|
||||
|
||||
export const useCommonSearch = (configPath) => {
|
||||
const [config, setConfig] = useState(null);
|
||||
const [searchParams, setSearchParams] = useState({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(null);
|
||||
const [configLoaded, setConfigLoaded] = useState(false);
|
||||
const { showToast } = useAlert();
|
||||
const [apiEndpoint, setApiEndpoint] = useState(null);
|
||||
const [apiConfig, setApiConfig] = useState(null);
|
||||
|
||||
// 설정 파일 로드
|
||||
useEffect(() => {
|
||||
const fetchConfig = async () => {
|
||||
try {
|
||||
const configData = await loadConfig(configPath);
|
||||
setConfig(configData);
|
||||
|
||||
// 초기 검색 파라미터 설정
|
||||
if (configData.initialSearchParams) {
|
||||
setSearchParams(configData.initialSearchParams);
|
||||
}
|
||||
|
||||
// API 엔드포인트 설정 가져오기
|
||||
if (configData.apiInfo && configData.apiInfo.endpointName) {
|
||||
const endpointName = configData.apiInfo.endpointName;
|
||||
// API 설정 파일에서 엔드포인트 정보 찾기
|
||||
for (const configKey in APIConfigs) {
|
||||
const apiConfigData = APIConfigs[configKey];
|
||||
if (apiConfigData.endpoints && apiConfigData.endpoints[endpointName]) {
|
||||
setApiEndpoint(apiConfigData.endpoints[endpointName]);
|
||||
setApiConfig(apiConfigData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setConfigLoaded(true);
|
||||
} catch (error) {
|
||||
console.error('Error loading search configuration:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchConfig();
|
||||
}, [configPath]);
|
||||
|
||||
// 파라미터 값 변환 (날짜 등)
|
||||
const transformParams = useCallback((params) => {
|
||||
if (!config || !config.apiInfo || !config.apiInfo.paramTransforms) {
|
||||
return params;
|
||||
}
|
||||
|
||||
const transformedParams = { ...params };
|
||||
|
||||
// 파라미터 변환 적용
|
||||
config.apiInfo.paramTransforms.forEach(paramConfig => {
|
||||
if (paramConfig.param && paramConfig.transform) {
|
||||
const value = params[paramConfig.param];
|
||||
|
||||
if (value) {
|
||||
if (paramConfig.transform === 'toISOString') {
|
||||
transformedParams[paramConfig.param] = new Date(value).toISOString();
|
||||
}
|
||||
// 필요시 다른 변환 로직 추가
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return transformedParams;
|
||||
}, [config]);
|
||||
|
||||
// 데이터 가져오기
|
||||
const fetchData = useCallback(async (params) => {
|
||||
if (!apiEndpoint || !apiConfig) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const transformedParams = transformParams(params);
|
||||
|
||||
// 페이지네이션 필드 옵션
|
||||
const paginationOptions = {
|
||||
paginationType: config.paginationType
|
||||
};
|
||||
if (config.apiInfo?.pageField) {
|
||||
paginationOptions.pageField = config.apiInfo.pageField;
|
||||
}
|
||||
if (config.apiInfo?.pageSizeField) {
|
||||
paginationOptions.pageSizeField = config.apiInfo.pageSizeField;
|
||||
}
|
||||
if (config.apiInfo?.orderField) {
|
||||
paginationOptions.orderField = config.apiInfo.orderField;
|
||||
}
|
||||
if (config.paginationType === 'dynamodb' && config.apiInfo?.lastPageKeyField) {
|
||||
paginationOptions.lastPageKeyField = config.apiInfo.lastPageKeyField;
|
||||
}
|
||||
|
||||
// API 호출
|
||||
const result = await callAPI(
|
||||
apiConfig,
|
||||
apiEndpoint,
|
||||
transformedParams,
|
||||
paginationOptions
|
||||
);
|
||||
|
||||
// 에러 처리
|
||||
if (result.result && result.result.startsWith('ERROR')) {
|
||||
showToast(result.data.message, { type: alertTypes.error });
|
||||
}
|
||||
// console.log(result.data);
|
||||
setData(result.data || result);
|
||||
return result.data || result;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching data:`, error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [apiEndpoint, apiConfig, config, transformParams, showToast]);
|
||||
|
||||
const initialLoad = useCallback((config) => {
|
||||
// loadOnMount가 undefined 또는 true인 경우 true 반환
|
||||
return config?.apiInfo?.loadOnMount === undefined ||
|
||||
config?.apiInfo?.loadOnMount === true;
|
||||
}, []);
|
||||
|
||||
// 초기 데이터 로드
|
||||
useEffect(() => {
|
||||
if (configLoaded && config && searchParams && apiEndpoint && config.apiInfo?.loadOnMount && initialLoad(config)) {
|
||||
fetchData(searchParams);
|
||||
}
|
||||
}, [configLoaded, config, searchParams, apiEndpoint, fetchData]);
|
||||
|
||||
// 검색 파라미터 업데이트
|
||||
const updateSearchParams = useCallback((newParams) => {
|
||||
setSearchParams(prev => ({
|
||||
...prev,
|
||||
...newParams
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// 검색 처리
|
||||
const handleSearch = useCallback(async (newParams = {}, executeSearch = true) => {
|
||||
if (!config) return null;
|
||||
|
||||
const pageField = 'currentPage'; // 항상 내부적으로는 currentPage 사용
|
||||
|
||||
const updatedParams = {
|
||||
...searchParams,
|
||||
...newParams,
|
||||
[pageField]: newParams[pageField] || 1 // 새 검색 시 첫 페이지로 리셋
|
||||
};
|
||||
|
||||
if (executeSearch && config.searchFields) {
|
||||
const requiredFields = config.searchFields.filter(field => field.required);
|
||||
|
||||
for (const field of requiredFields) {
|
||||
if (!updatedParams[field.id] || updatedParams[field.id].trim() === '') {
|
||||
// 필수 필드가 비어있는 경우
|
||||
showToast('SEARCH_REQUIRED_WARNING', { type: alertTypes.warning });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateSearchParams(updatedParams);
|
||||
|
||||
if (executeSearch) {
|
||||
return await fetchData(updatedParams);
|
||||
}
|
||||
return null;
|
||||
}, [searchParams, fetchData, config, updateSearchParams]);
|
||||
|
||||
// 검색 초기화
|
||||
const handleReset = useCallback(async () => {
|
||||
if (!config || !config.initialSearchParams) return null;
|
||||
|
||||
setSearchParams(config.initialSearchParams);
|
||||
setData(null);
|
||||
// return await fetchData(config.initialSearchParams);
|
||||
}, [config, fetchData]);
|
||||
|
||||
// 페이지 변경
|
||||
const handlePageChange = useCallback(async (newPage) => {
|
||||
if (!config) return null;
|
||||
|
||||
return await handleSearch({ currentPage: newPage }, true);
|
||||
}, [handleSearch, config]);
|
||||
|
||||
// 페이지 크기 변경
|
||||
const handlePageSizeChange = useCallback(async (newSize) => {
|
||||
if (!config) return null;
|
||||
|
||||
return await handleSearch({
|
||||
pageSize: newSize,
|
||||
currentPage: 1
|
||||
}, true);
|
||||
}, [handleSearch, config]);
|
||||
|
||||
// 정렬 방식 변경
|
||||
const handleOrderByChange = useCallback(async (newOrder) => {
|
||||
if (!config) return null;
|
||||
|
||||
return await handleSearch({ orderBy: newOrder }, true);
|
||||
}, [handleSearch, config]);
|
||||
|
||||
return {
|
||||
config,
|
||||
searchParams,
|
||||
loading,
|
||||
data,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
handleOrderByChange,
|
||||
updateSearchParams,
|
||||
configLoaded
|
||||
};
|
||||
};
|
||||
|
||||
export default useCommonSearch;
|
||||
@@ -1,16 +1,17 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { loadConfig } from '../../../utils';
|
||||
import * as APIs from '../../../apis';
|
||||
import { useAlert } from '../../../context/AlertProvider';
|
||||
import { alertTypes } from '../../../assets/data/types';
|
||||
import { loadConfig } from '../utils';
|
||||
import * as APIs from '../apis';
|
||||
import { useAlert } from '../context/AlertProvider';
|
||||
import { alertTypes } from '../assets/data/types';
|
||||
|
||||
export const useCommonSearch = (token, configPath) => {
|
||||
export const useCommonSearchOld = (configPath) => {
|
||||
const [config, setConfig] = useState(null);
|
||||
const [searchParams, setSearchParams] = useState({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(null);
|
||||
const [configLoaded, setConfigLoaded] = useState(false);
|
||||
const { showToast } = useAlert();
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
// 설정 파일 로드
|
||||
useEffect(() => {
|
||||
@@ -179,4 +180,4 @@ export const useCommonSearch = (token, configPath) => {
|
||||
};
|
||||
};
|
||||
|
||||
export default useCommonSearch;
|
||||
export default useCommonSearchOld;
|
||||
190
src/hooks/useDynamoDBPagination.js
Normal file
190
src/hooks/useDynamoDBPagination.js
Normal file
@@ -0,0 +1,190 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
/**
|
||||
* DynamoDB 스타일의 페이지네이션을 위한 훅
|
||||
* LastEvaluatedKey 기반의 페이지네이션 방식
|
||||
*
|
||||
* @param {Function} fetchFunction 데이터를 가져오는 함수 (페이지와 키 파라미터를 받음)
|
||||
* @param {Object} initialState 초기 페이지네이션 상태
|
||||
* @returns {Object} 페이지네이션 관련 상태와 메서드
|
||||
*/
|
||||
export const useDynamoDBPagination = (fetchFunction, initialState = {}) => {
|
||||
// 페이지네이션 상태
|
||||
const [pagination, setPagination] = useState({
|
||||
currentPage: initialState.currentPage || 1,
|
||||
pageKeys: initialState.pageKeys || { 1: null },
|
||||
hasNextPage: initialState.hasNextPage || false,
|
||||
items: initialState.items || [], // 현재까지 로드된 아이템들
|
||||
itemCount: initialState.itemCount || 0 // 현재 페이지 아이템 수
|
||||
});
|
||||
|
||||
// 로딩 상태
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
/**
|
||||
* 특정 페이지로 이동
|
||||
* @param {number} page 이동할 페이지 번호
|
||||
* @returns {Promise} 데이터 가져오기 결과
|
||||
*/
|
||||
const goToPage = useCallback(async (page) => {
|
||||
// DynamoDB에서는 임의 페이지로 이동할 수 없음 (pagination.pageKeys에 해당 페이지의 키가 있는 경우만 가능)
|
||||
if (page < 1 || (page > pagination.currentPage + 1 && !pagination.pageKeys[page])) {
|
||||
console.warn('DynamoDB 페이지네이션에서는 임의 페이지로 이동할 수 없습니다.');
|
||||
return null;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// 해당 페이지의 시작 키
|
||||
const startKey = pagination.pageKeys[page];
|
||||
|
||||
// 데이터 가져오기
|
||||
const response = await fetchFunction({
|
||||
currentPage: page,
|
||||
lastEvaluatedKey: startKey
|
||||
});
|
||||
|
||||
// 응답에서 페이지네이션 정보 추출
|
||||
const pageKey = response.LastEvaluatedKey || response.lastEvaluatedKey || response.pageKey;
|
||||
const items = response.items || response.list || response.data || [];
|
||||
|
||||
// 페이지네이션 정보 업데이트
|
||||
setPagination(prev => {
|
||||
const updatedPageKeys = { ...prev.pageKeys };
|
||||
|
||||
// 다음 페이지를 위한 키 저장
|
||||
if (pageKey) {
|
||||
updatedPageKeys[page + 1] = pageKey;
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
currentPage: page,
|
||||
pageKeys: updatedPageKeys,
|
||||
hasNextPage: !!pageKey,
|
||||
items,
|
||||
itemCount: items.length
|
||||
};
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('페이지 데이터 가져오기 오류:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [fetchFunction, pagination.currentPage, pagination.pageKeys]);
|
||||
|
||||
/**
|
||||
* 다음 페이지로 이동
|
||||
* @returns {Promise} 데이터 가져오기 결과
|
||||
*/
|
||||
const goToNextPage = useCallback(() => {
|
||||
if (!pagination.hasNextPage) return null;
|
||||
return goToPage(pagination.currentPage + 1);
|
||||
}, [pagination.hasNextPage, pagination.currentPage, goToPage]);
|
||||
|
||||
/**
|
||||
* 이전 페이지로 이동
|
||||
* @returns {Promise} 데이터 가져오기 결과
|
||||
*/
|
||||
const goToPrevPage = useCallback(() => {
|
||||
if (pagination.currentPage <= 1) return null;
|
||||
return goToPage(pagination.currentPage - 1);
|
||||
}, [pagination.currentPage, goToPage]);
|
||||
|
||||
/**
|
||||
* 페이지네이션 리셋
|
||||
* @returns {Promise} 데이터 가져오기 결과
|
||||
*/
|
||||
const resetPagination = useCallback(async () => {
|
||||
setPagination({
|
||||
currentPage: 1,
|
||||
pageKeys: { 1: null },
|
||||
hasNextPage: false,
|
||||
items: [],
|
||||
itemCount: 0
|
||||
});
|
||||
|
||||
// return goToPage(1);
|
||||
}, [goToPage]);
|
||||
|
||||
/**
|
||||
* 새 검색 시작
|
||||
* 키워드가 변경되면 페이지네이션 상태 초기화
|
||||
* @param {Object} searchParams 검색 파라미터
|
||||
* @returns {Promise} 데이터 가져오기 결과
|
||||
*/
|
||||
const newSearch = useCallback(async (searchParams) => {
|
||||
setPagination({
|
||||
currentPage: 1,
|
||||
pageKeys: { 1: null },
|
||||
hasNextPage: false,
|
||||
items: [],
|
||||
itemCount: 0
|
||||
});
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetchFunction({
|
||||
...searchParams,
|
||||
currentPage: 1,
|
||||
lastEvaluatedKey: null
|
||||
});
|
||||
|
||||
if (!response) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 응답에서 페이지네이션 정보 추출
|
||||
const pageKey = response.lastEvaluatedKey || response.pageKey;
|
||||
const items = response.items || response.list || response.data || [];
|
||||
|
||||
setPagination({
|
||||
currentPage: 1,
|
||||
pageKeys: {
|
||||
1: null,
|
||||
...(pageKey ? { 2: pageKey } : {})
|
||||
},
|
||||
hasNextPage: !!pageKey,
|
||||
items,
|
||||
itemCount: items.length
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('새 검색 오류:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [fetchFunction]);
|
||||
|
||||
return {
|
||||
type: 'dynamodb',
|
||||
pagination,
|
||||
loading,
|
||||
goToPage,
|
||||
goToNextPage,
|
||||
goToPrevPage,
|
||||
resetPagination,
|
||||
newSearch,
|
||||
|
||||
// 레거시 호환성을 위한 별칭
|
||||
// (DynamoDB는 임의 페이지 이동을 지원하지 않지만, 호환성을 위해 제공)
|
||||
handlePageChange: (page) => {
|
||||
console.warn('DynamoDB 페이지네이션에서 handlePageChange는 제한적으로 동작합니다');
|
||||
if (page === pagination.currentPage + 1) {
|
||||
return goToNextPage();
|
||||
} else if (page === pagination.currentPage - 1) {
|
||||
return goToPrevPage();
|
||||
} else if (page === 1) {
|
||||
return resetPagination();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default useDynamoDBPagination;
|
||||
118
src/hooks/useEnhancedCommonSearch.js
Normal file
118
src/hooks/useEnhancedCommonSearch.js
Normal file
@@ -0,0 +1,118 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCommonSearch } from './useCommonSearch';
|
||||
import useRDSPagination from './useRDSPagination';
|
||||
import useDynamoDBPagination from './useDynamoDBPagination';
|
||||
|
||||
/**
|
||||
* 확장된 CommonSearch 훅
|
||||
* 설정에 따라 RDS 또는 DynamoDB 페이지네이션을 사용
|
||||
*
|
||||
* @param {string} configPath 설정 파일 경로
|
||||
* @returns {Object} 검색 및 페이지네이션 관련 상태와 메서드
|
||||
*/
|
||||
export const useEnhancedCommonSearch = (configPath) => {
|
||||
const [paginationType, setPaginationType] = useState(null);
|
||||
|
||||
// 기본 검색 기능
|
||||
const commonSearch = useCommonSearch(configPath);
|
||||
const {
|
||||
config,
|
||||
searchParams,
|
||||
data,
|
||||
loading: searchLoading,
|
||||
handleSearch: originalHandleSearch,
|
||||
updateSearchParams,
|
||||
handleReset: originalHandleReset,
|
||||
configLoaded
|
||||
} = commonSearch;
|
||||
|
||||
// 설정에서 페이지네이션 타입 가져오기
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
setPaginationType(config.paginationType || 'rds');
|
||||
}
|
||||
}, [config]);
|
||||
|
||||
// RDS 페이지네이션
|
||||
const rdsPagenation = useRDSPagination(async (pageParams) => {
|
||||
return await originalHandleSearch(pageParams, true);
|
||||
}, {
|
||||
currentPage: searchParams.currentPage,
|
||||
pageSize: searchParams.pageSize,
|
||||
totalItems: data?.total_all || data?.total || 0,
|
||||
totalPages: data?.total_pages || Math.ceil((data?.total_all || data?.total || 0) / searchParams.pageSize)
|
||||
});
|
||||
|
||||
// DynamoDB 페이지네이션
|
||||
const dynamoDBPagination = useDynamoDBPagination(async (pageParams) => {
|
||||
return await originalHandleSearch(pageParams, true);
|
||||
}, {
|
||||
currentPage: searchParams.currentPage,
|
||||
pageKeys: { 1: null },
|
||||
hasNextPage: !!(data?.LastEvaluatedKey || data?.lastEvaluatedKey || data?.pageKey),
|
||||
items: data?.list || data?.items || [],
|
||||
itemCount: (data?.list || data?.items || []).length
|
||||
});
|
||||
|
||||
// 현재 페이지네이션 타입에 따른 핸들러
|
||||
const paginationHandler = paginationType === 'dynamodb' ? dynamoDBPagination : rdsPagenation;
|
||||
|
||||
// 검색 핸들러 (페이지네이션 리셋 포함)
|
||||
const handleSearch = async (newParams = {}, executeSearch = true) => {
|
||||
if (!executeSearch) {
|
||||
updateSearchParams(newParams);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 키워드 검색 시 페이지네이션 리셋
|
||||
if (paginationType === 'dynamodb') {
|
||||
return await dynamoDBPagination.newSearch({
|
||||
...searchParams,
|
||||
...newParams
|
||||
});
|
||||
} else {
|
||||
updateSearchParams(newParams);
|
||||
return await rdsPagenation.goToPage(1);
|
||||
}
|
||||
};
|
||||
|
||||
// 리셋 핸들러
|
||||
const handleReset = async () => {
|
||||
await originalHandleReset();
|
||||
if (paginationType === 'dynamodb') {
|
||||
return await dynamoDBPagination.resetPagination();
|
||||
}
|
||||
// else {
|
||||
// await rdsPagenation.resetPagination();
|
||||
// }
|
||||
};
|
||||
|
||||
return {
|
||||
// 기본 검색 상태
|
||||
config,
|
||||
searchParams,
|
||||
data,
|
||||
loading: searchLoading || paginationHandler.loading,
|
||||
configLoaded,
|
||||
updateSearchParams,
|
||||
|
||||
// 페이지네이션 타입 및 상태
|
||||
paginationType,
|
||||
pagination: paginationHandler.pagination,
|
||||
|
||||
// 통합된 핸들러
|
||||
handleSearch,
|
||||
handleReset,
|
||||
|
||||
// 페이지네이션 핸들러
|
||||
handlePageChange: paginationHandler.goToPage,
|
||||
goToNextPage: paginationHandler.goToNextPage,
|
||||
goToPrevPage: paginationHandler.goToPrevPage,
|
||||
|
||||
// RDS 전용 핸들러
|
||||
handlePageSizeChange: rdsPagenation.changePageSize,
|
||||
handleOrderByChange: commonSearch.handleOrderByChange
|
||||
};
|
||||
};
|
||||
|
||||
export default useEnhancedCommonSearch;
|
||||
129
src/hooks/useRDSPagination.js
Normal file
129
src/hooks/useRDSPagination.js
Normal file
@@ -0,0 +1,129 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
/**
|
||||
* RDS 스타일의 페이지네이션을 위한 훅
|
||||
* 페이지 번호와 페이지 크기 기반의 일반적인 페이지네이션 방식
|
||||
*
|
||||
* @param {Function} fetchFunction 데이터를 가져오는 함수 (페이지 파라미터를 받음)
|
||||
* @param {Object} initialState 초기 페이지네이션 상태
|
||||
* @returns {Object} 페이지네이션 관련 상태와 메서드
|
||||
*/
|
||||
export const useRDSPagination = (fetchFunction, initialState = {}) => {
|
||||
// 페이지네이션 상태
|
||||
const [pagination, setPagination] = useState({
|
||||
currentPage: initialState.currentPage || 1,
|
||||
pageSize: initialState.pageSize || 10,
|
||||
totalItems: initialState.totalItems || 0,
|
||||
totalPages: initialState.totalPages || 0
|
||||
});
|
||||
|
||||
// 로딩 상태
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
/**
|
||||
* 특정 페이지로 이동
|
||||
* @param {number} page 이동할 페이지 번호
|
||||
* @returns {Promise} 데이터 가져오기 결과
|
||||
*/
|
||||
const goToPage = useCallback(async (page) => {
|
||||
if (page < 1) return null;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// 페이지 정보를 포함하여 데이터 가져오기
|
||||
const response = await fetchFunction({
|
||||
currentPage: page,
|
||||
pageSize: pagination.pageSize
|
||||
});
|
||||
|
||||
// 페이지네이션 정보 업데이트
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
currentPage: page,
|
||||
totalItems: response.total_all || response.total || 0,
|
||||
totalPages: response.total_pages || Math.ceil((response.total_all || response.total || 0) / pagination.pageSize)
|
||||
}));
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('페이지 데이터 가져오기 오류:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [fetchFunction, pagination.pageSize]);
|
||||
|
||||
/**
|
||||
* 다음 페이지로 이동
|
||||
* @returns {Promise} 데이터 가져오기 결과
|
||||
*/
|
||||
const goToNextPage = useCallback(() => {
|
||||
if (pagination.currentPage >= pagination.totalPages) return null;
|
||||
return goToPage(pagination.currentPage + 1);
|
||||
}, [pagination.currentPage, pagination.totalPages, goToPage]);
|
||||
|
||||
/**
|
||||
* 이전 페이지로 이동
|
||||
* @returns {Promise} 데이터 가져오기 결과
|
||||
*/
|
||||
const goToPrevPage = useCallback(() => {
|
||||
if (pagination.currentPage <= 1) return null;
|
||||
return goToPage(pagination.currentPage - 1);
|
||||
}, [pagination.currentPage, goToPage]);
|
||||
|
||||
/**
|
||||
* 페이지 크기 변경
|
||||
* @param {number} newSize 새 페이지 크기
|
||||
* @returns {Promise} 데이터 가져오기 결과
|
||||
*/
|
||||
const changePageSize = useCallback(async (newSize) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetchFunction({
|
||||
currentPage: 1, // 페이지 크기 변경 시 첫 페이지로 이동
|
||||
pageSize: newSize
|
||||
});
|
||||
|
||||
// 페이지네이션 정보 업데이트
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
currentPage: 1,
|
||||
pageSize: newSize,
|
||||
totalItems: response.total_all || response.total || 0,
|
||||
totalPages: response.total_pages || Math.ceil((response.total_all || response.total || 0) / newSize)
|
||||
}));
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('페이지 크기 변경 오류:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [fetchFunction]);
|
||||
|
||||
/**
|
||||
* 페이지네이션 리셋
|
||||
* @returns {Promise} 데이터 가져오기 결과
|
||||
*/
|
||||
const resetPagination = useCallback(async () => {
|
||||
return goToPage(1);
|
||||
}, [goToPage]);
|
||||
|
||||
return {
|
||||
type: 'rds',
|
||||
pagination,
|
||||
loading,
|
||||
goToPage,
|
||||
goToNextPage,
|
||||
goToPrevPage,
|
||||
changePageSize,
|
||||
resetPagination,
|
||||
|
||||
// 레거시 호환성을 위한 별칭
|
||||
handlePageChange: goToPage,
|
||||
handlePageSizeChange: changePageSize
|
||||
};
|
||||
};
|
||||
|
||||
export default useRDSPagination;
|
||||
@@ -43,6 +43,7 @@ const resources = {
|
||||
WARNING_EMAIL_CHECK: '이메일을 확인해주세요.',
|
||||
WARNING_TYPE_CHECK: '타입을 확인해주세요.',
|
||||
DATE_START_DIFF_END_WARNING :"종료일은 시작일보다 하루 이후여야 합니다.",
|
||||
SEARCH_REQUIRED_WARNING:"필수 입력 항목을 확인해주세요.",
|
||||
//table
|
||||
TABLE_ITEM_DELETE_TITLE: "선택 삭제",
|
||||
TABLE_BUTTON_DETAIL_TITLE: "상세보기",
|
||||
@@ -126,6 +127,10 @@ const resources = {
|
||||
MENU_BANNER_REGIST_CONFIRM: "배너를 등록하시겠습니까?",
|
||||
MENU_BANNER_SELECT_DELETE: "선택된 배너를 삭제하시겠습니까?",
|
||||
MENU_BANNER_REGIST_CANCEL: "배너 등록을 취소하시겠습니까?\n\r취소 시 설정된 값은 반영되지 않습니다.",
|
||||
//아이템
|
||||
ITEM_DELETE_CONFIRM: '해당 아이템을 삭제하시겠습니까?\r\n* 한번 삭제한 아이템은 다시 복구할 수 없습니다.',
|
||||
ITEM_RESTORE_CONFIRM: '해당 아이템을 복구하시겠습니까?',
|
||||
ITEM_RESTORE_COMPLETE: '복구가 완료되었습니다.',
|
||||
// 이용자 제재
|
||||
USER_BLOCK_VALIDATION_WARNING: '유효성 체크가 통과되지 않은 항목이 존재합니다.\r\n수정 후 재등록 해주세요.',
|
||||
USER_BLOCK_REGIST_DUPLE_WARNING: '이미 제재가 등록된 유저입니다.',
|
||||
@@ -141,7 +146,9 @@ const resources = {
|
||||
FILE_CALIUM_REQUEST: 'Caliverse_Calium_Request.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',
|
||||
//서버 에러메시지
|
||||
DYNAMODB_NOT_USER: '유저 정보를 확인해주세요.'
|
||||
}
|
||||
},
|
||||
en: {
|
||||
|
||||
@@ -32,6 +32,7 @@ import { CommonSearchBar, useCommonSearch } from '../../components/ServiceManage
|
||||
import { useModal, useTable } from '../../hooks/hook';
|
||||
import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import useCommonSearchOld from '../../hooks/useCommonSearchOld';
|
||||
|
||||
const Event = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
@@ -64,7 +65,7 @@ const Event = () => {
|
||||
handleOrderByChange,
|
||||
updateSearchParams,
|
||||
configLoaded
|
||||
} = useCommonSearch(token, "eventSearch");
|
||||
} = useCommonSearchOld("eventSearch");
|
||||
const {
|
||||
selectedRows,
|
||||
handleSelectRow,
|
||||
|
||||
@@ -1,252 +1,207 @@
|
||||
import { useState, Fragment, useEffect } from 'react';
|
||||
import Modal from '../../components/common/modal/Modal';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import styled from 'styled-components';
|
||||
import { Title, FormWrapper, TableStyle, TableWrapper, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ItemsSearchBar } from '../../components/ServiceManage';
|
||||
import { ItemListView } from '../../apis';
|
||||
import React, { Fragment, useState } from 'react';
|
||||
|
||||
import { Title, FormWrapper, InputItem, TextInput } from '../../styles/Components';
|
||||
import { CommonSearchBar, useCommonSearch } from '../../components/ServiceManage';
|
||||
import { ItemDeleteAPI } from '../../apis';
|
||||
|
||||
import { authList } from '../../store/authList';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import AuthModal from '../../components/common/modal/AuthModal';
|
||||
import { authType } from '../../assets/data';
|
||||
import { CaliTable, TableHeader } from '../../components/common';
|
||||
import tableInfo from '../../assets/data/pages/itemTable.json';
|
||||
import { useModal, useTable, withAuth } from '../../hooks/hook';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import useEnhancedCommonSearch from '../../hooks/useEnhancedCommonSearch';
|
||||
import CustomConfirmModal from '../../components/common/modal/CustomConfirmModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const Items = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const {showModal, showToast} = useAlert();
|
||||
const {withLoading} = useLoading();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 데이터 조회 관련
|
||||
const [dataList, setDataList] = useState([]);
|
||||
const [selectedData, setSelectedData] = useState([]);
|
||||
const [requestValue, setRequestValue] = useState([]);
|
||||
|
||||
// 모달 관련 변수
|
||||
const [confirmDelModal, setConfirmDelModal] = useState('hidden');
|
||||
const [completeDelModal, setCompleteDelModal] = useState('hidden');
|
||||
const [confirmRestoreModal, setConfirmRestoreModal] = useState('hidden');
|
||||
const [completeRestoreModal, setCompleteRestoreModal] = useState('hidden');
|
||||
|
||||
// 검색 관련 변수
|
||||
const [searchData, setSearchData] = useState({
|
||||
searchType: 'GUID',
|
||||
searchKey: '',
|
||||
status: 'ALL',
|
||||
sanctions: 'ALL',
|
||||
period: 'ALL',
|
||||
const [itemCount, setItemCount] = useState('1');
|
||||
const {
|
||||
modalState,
|
||||
handleModalView,
|
||||
handleModalClose
|
||||
} = useModal({
|
||||
delete: 'hidden',
|
||||
});
|
||||
|
||||
const status = [
|
||||
{ value: 'ALL', name: '상태' },
|
||||
{ value: 'ACTIVE', name: '활성' },
|
||||
{ value: 'DEACTIVE', name: '비활성' },
|
||||
];
|
||||
|
||||
const restore = [
|
||||
{ value: 'ALL', name: '복구' },
|
||||
{ value: 'POSSIBLE', name: '가능' },
|
||||
{ value: 'IMPOSSIBLE', name: '불가능' },
|
||||
];
|
||||
const {
|
||||
config,
|
||||
searchParams,
|
||||
data: dataList,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
updateSearchParams,
|
||||
loading,
|
||||
configLoaded,
|
||||
paginationType,
|
||||
pagination,
|
||||
goToNextPage,
|
||||
goToPrevPage,
|
||||
} = useEnhancedCommonSearch("itemSearch");
|
||||
|
||||
const fetchData = async (searchType, data, status, restore) => {
|
||||
setDataList(await ItemListView(token, searchType, data, status, restore));
|
||||
};
|
||||
const {
|
||||
selectedRows,
|
||||
handleSelectRow,
|
||||
isRowSelected
|
||||
} = useTable(dataList?.list || [], {mode: 'single'});
|
||||
|
||||
// 검색 기능
|
||||
const handleSearch = (searchType, data, status, restore) => {
|
||||
fetchData(searchType, data, status, restore);
|
||||
};
|
||||
const handleAction = async (action, item = null) => {
|
||||
switch (action) {
|
||||
case "delete":
|
||||
handleModalView('delete');
|
||||
break;
|
||||
case 'deleteCountConfirm':
|
||||
showModal('ITEM_DELETE_CONFIRM', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleAction('deleteConfirm')
|
||||
});
|
||||
break;
|
||||
case "deleteConfirm":
|
||||
handleModalClose('delete');
|
||||
const selectRow = selectedRows[0];
|
||||
let params = {};
|
||||
params.user_guid = selectRow.user_guid;
|
||||
params.item_guid = selectRow.item_guid;
|
||||
params.item_count = selectRow.count;
|
||||
params.delete_count = itemCount;
|
||||
|
||||
// 삭제 여부 모달
|
||||
const handleConfirmeDelModalClose = () => {
|
||||
if (confirmDelModal === 'hidden') {
|
||||
setConfirmDelModal('view');
|
||||
} else {
|
||||
setConfirmDelModal('hidden');
|
||||
setRequestValue([]);
|
||||
await withLoading(async () => {
|
||||
return await ItemDeleteAPI(token, params);
|
||||
}).then(data => {
|
||||
if(data.result === "SUCCESS") {
|
||||
showToast('DEL_COMPLETE', {type: alertTypes.success});
|
||||
}else{
|
||||
showToast('DELETE_FAIL', {type: alertTypes.error});
|
||||
}
|
||||
};
|
||||
}).catch(reason => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
setItemCount('1');
|
||||
handleSearch(updateSearchParams);
|
||||
});
|
||||
|
||||
// 복구 여부 모달
|
||||
const handleConfirmeRestoreModalClose = () => {
|
||||
if (confirmRestoreModal === 'hidden') {
|
||||
setConfirmRestoreModal('view');
|
||||
} else {
|
||||
setConfirmRestoreModal('hidden');
|
||||
setRequestValue([]);
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
// 삭제 모달창
|
||||
const handleConfirmDeleteModal = data => {
|
||||
handleConfirmeDelModalClose();
|
||||
};
|
||||
|
||||
// 복구 모달창
|
||||
const handleConfirmRestoreModal = data => {
|
||||
handleConfirmeRestoreModalClose();
|
||||
};
|
||||
|
||||
// 삭제 완료 확인 모달
|
||||
const handleCompleteDelModal = () => {
|
||||
if (completeDelModal === 'hidden') {
|
||||
setCompleteDelModal('view');
|
||||
} else {
|
||||
setCompleteDelModal('hidden');
|
||||
setRequestValue([]);
|
||||
handleConfirmeDelModalClose();
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// 복구 완료 확인 모달
|
||||
const handleCompleteRestoreModal = () => {
|
||||
if (completeRestoreModal === 'hidden') {
|
||||
setCompleteRestoreModal('view');
|
||||
} else {
|
||||
setCompleteRestoreModal('hidden');
|
||||
setRequestValue([]);
|
||||
handleConfirmeRestoreModalClose();
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// 삭제 기능 구현
|
||||
const handleDelete = () => {
|
||||
case "restore":
|
||||
showModal('ITEM_RESTORE_CONFIRM', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleAction('restoreConfirm')
|
||||
});
|
||||
break;
|
||||
case "restoreConfirm":
|
||||
let list = [];
|
||||
|
||||
list.push({ id: selectedData });
|
||||
selectedRows.map(data => {
|
||||
list.push({
|
||||
id: data.id,
|
||||
});
|
||||
});
|
||||
|
||||
// BlackListDelete(token, list);
|
||||
handleCompleteDelModal();
|
||||
await withLoading(async () => {
|
||||
return await ItemDeleteAPI(token, list);
|
||||
}).then(data => {
|
||||
if(data.result === "SUCCESS") {
|
||||
showToast('ITEM_RESTORE_COMPLETE', {type: alertTypes.success});
|
||||
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){
|
||||
showToast('LAND_AUCTION_ERROR_DELETE_STATUS', {type: alertTypes.error});
|
||||
}else{
|
||||
showToast('DELETE_FAIL', {type: alertTypes.error});
|
||||
}
|
||||
}).catch(reason => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
handleSearch(updateSearchParams);
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// 복구 기능
|
||||
const handleRestore = () => {
|
||||
let list = [];
|
||||
const handleItemCount = e => {
|
||||
const selectRowCount = selectedRows[0]?.count;
|
||||
if (e.target.value === '0' || e.target.value === '-0') {
|
||||
setItemCount('1');
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setItemCount(plusNum);
|
||||
} else if(e.target.value > selectRowCount){
|
||||
showToast('DEL_COUNT_CHECK',{type:alertTypes.warning});
|
||||
setItemCount(selectRowCount);
|
||||
} else {
|
||||
setItemCount(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
list.push({ id: selectedData});
|
||||
//api 호출
|
||||
|
||||
handleCompleteRestoreModal();
|
||||
const ConfirmChild = () => {
|
||||
if(selectedRows === undefined || selectedRows.length === 0) return;
|
||||
const selectRow = selectedRows[0];
|
||||
console.log(selectRow)
|
||||
return(
|
||||
<InputItem>
|
||||
<p>{t('DEL_COUNT_CONFIRM', {count: selectRow?.count})}</p>
|
||||
<TextInput placeholder="수량" type="number" value={itemCount} onChange={e => handleItemCount(e)} width="200px" />
|
||||
</InputItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.itemRead) ? (
|
||||
<AuthModal/>
|
||||
) : (
|
||||
<>
|
||||
<Title>아이템 복구 및 삭제</Title>
|
||||
<Title>아이템 관리</Title>
|
||||
<FormWrapper>
|
||||
<ItemsSearchBar handleSearch={handleSearch} setResultData={setSearchData} />
|
||||
<CommonSearchBar
|
||||
config={config}
|
||||
searchParams={searchParams}
|
||||
onSearch={(newParams, executeSearch = true) => {
|
||||
if (executeSearch) {
|
||||
handleSearch(newParams);
|
||||
} else {
|
||||
updateSearchParams(newParams);
|
||||
}
|
||||
}}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
</FormWrapper>
|
||||
<TableWrapper>
|
||||
<TableStyle>
|
||||
<caption></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="80">번호</th>
|
||||
<th width="20%">아이템명</th>
|
||||
<th width="20%">아이템 ID</th>
|
||||
<th width="80">상태</th>
|
||||
<th width="100">생성 날짜</th>
|
||||
<th width="80">복구 가능 여부</th>
|
||||
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 30) && <th width="100">액션</th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList.list &&
|
||||
dataList.list.map(data => (
|
||||
<Fragment key={data.id}>
|
||||
<tr>
|
||||
<td>{data.row_num}</td>
|
||||
<td>{data.item_nm}</td>
|
||||
<td>{data.item_id}</td>
|
||||
<td>{status.map(item => item.value === data.status && item.name)}</td>
|
||||
<td>{new Date(data.create_by).toLocaleString()}</td>
|
||||
<td>{restore.map(item => item.value === data.restore && item.name)}</td>
|
||||
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 30) && (
|
||||
<td>
|
||||
<ButtonGroup>
|
||||
<Button theme="line" id={data.id+"restore"} name="single" text="복구" handleClick={() => handleConfirmRestoreModal(data)} />
|
||||
<Divider>/</Divider>
|
||||
<Button theme="line" id={data.id+"delete"} name="single" text="삭제" handleClick={() => handleConfirmDeleteModal(data)} />
|
||||
</ButtonGroup>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
</Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</TableWrapper>
|
||||
|
||||
{/* 삭제 확인 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={confirmDelModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleConfirmeDelModalClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
아이템을 삭제하시겠습니까?<br />* 한번 삭제한 아이템은 다시 복구할 수 없습니다.
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleConfirmeDelModalClose} />
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleDelete} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
{/* 조회헤더 */}
|
||||
<TableHeader
|
||||
config={tableInfo.header}
|
||||
selectedRows={selectedRows}
|
||||
onAction={handleAction}
|
||||
pagination={pagination}
|
||||
goToNextPage={goToNextPage}
|
||||
goToPrevPage={goToPrevPage}
|
||||
/>
|
||||
|
||||
{/* 삭제 완료 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={completeDelModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleCompleteDelModal} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">삭제가 완료되었습니다.</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleCompleteDelModal} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
{/* 조회테이블 */}
|
||||
<CaliTable
|
||||
columns={tableInfo.columns}
|
||||
data={dataList?.list}
|
||||
onSelectRow={handleSelectRow}
|
||||
isRowSelected={isRowSelected}
|
||||
onAction={handleAction}
|
||||
loading={loading}
|
||||
/>
|
||||
|
||||
{/* 복구 확인 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={confirmRestoreModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleConfirmeRestoreModalClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
아이템을 복구하시겠습니까?
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleConfirmeRestoreModalClose} />
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleDelete} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 복구 완료 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={completeRestoreModal}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleCompleteRestoreModal} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">복구가 완료되었습니다.</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleCompleteRestoreModal} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
)}
|
||||
<CustomConfirmModal
|
||||
ChildView={ConfirmChild}
|
||||
view={modalState.deleteModal}
|
||||
handleSubmit={() => handleAction('deleteCountConfirm')}
|
||||
handleCancel={() => handleModalClose('delete')}
|
||||
handleClose={() => handleModalClose('delete')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Items;
|
||||
|
||||
const ButtonGroup = styled.div`
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
`
|
||||
const Divider = styled.span`
|
||||
margin: 0 8px;
|
||||
color: RGB(200,200,200);
|
||||
padding: 4px 0;
|
||||
`;
|
||||
export default withAuth(authType.itemRead)(Items);
|
||||
|
||||
@@ -31,6 +31,7 @@ import { alertTypes } from '../../assets/data/types';
|
||||
import { useModal, useTable, withAuth } from '../../hooks/hook';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import useCommonSearchOld from '../../hooks/useCommonSearchOld';
|
||||
|
||||
const Mail = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
@@ -60,7 +61,7 @@ const Mail = () => {
|
||||
handleOrderByChange,
|
||||
updateSearchParams,
|
||||
configLoaded
|
||||
} = useCommonSearch(token, "mailSearch");
|
||||
} = useCommonSearchOld("mailSearch");
|
||||
|
||||
const {
|
||||
selectedRows,
|
||||
|
||||
@@ -1,38 +1,33 @@
|
||||
import { useState, Fragment, useRef } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
|
||||
import { authList } from '../../store/authList';
|
||||
import {
|
||||
authType,
|
||||
modalTypes,
|
||||
landAuctionStatusType, opYNType,
|
||||
} from '../../assets/data';
|
||||
import { Title, FormWrapper, TableStyle, TableWrapper} from '../../styles/Components';
|
||||
import { Title, FormWrapper} from '../../styles/Components';
|
||||
import {
|
||||
CheckBox,
|
||||
Button,
|
||||
DynamicModal,
|
||||
Pagination,
|
||||
ViewTableInfo,
|
||||
CaliTable, TableHeader,
|
||||
} from '../../components/common';
|
||||
import { convertKTC, timeDiffMinute } from '../../utils';
|
||||
import { INITIAL_PAGE_SIZE, INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
||||
import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
||||
import { useModal, useTable, withAuth } from '../../hooks/hook';
|
||||
import { StatusWapper, StatusLabel } from '../../styles/ModuleComponents';
|
||||
import { opMenuBannerStatus } from '../../assets/data/options';
|
||||
import MenuBannerSearchBar, { useMenuBannerSearch } from '../../components/ServiceManage/searchBar/MenuBannerSearchBar';
|
||||
import { MenuBannerDelete, MenuBannerDetailView } from '../../apis';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import MenuBannerModal from '../../components/ServiceManage/modal/MenuBannerModal';
|
||||
import tableInfo from '../../assets/data/pages/menuBannerTable.json'
|
||||
import { CommonSearchBar, useCommonSearch } from '../../components/ServiceManage';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import useEnhancedCommonSearch from '../../hooks/useEnhancedCommonSearch';
|
||||
|
||||
const MenuBanner = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const { t } = useTranslation();
|
||||
const tableRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
const { showToast, showModal } = useAlert();
|
||||
const {withLoading} = useLoading();
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const [detailData, setDetailData] = useState({});
|
||||
|
||||
@@ -42,22 +37,26 @@ const MenuBanner = () => {
|
||||
handleModalClose
|
||||
} = useModal({
|
||||
detail: 'hidden',
|
||||
deleteConfirm: 'hidden',
|
||||
deleteComplete: 'hidden'
|
||||
});
|
||||
const [alertMsg, setAlertMsg] = useState('');
|
||||
const [modalType, setModalType] = useState('regist');
|
||||
|
||||
const {
|
||||
config,
|
||||
searchParams,
|
||||
data: dataList,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
handleOrderByChange,
|
||||
updateSearchParams
|
||||
} = useMenuBannerSearch(token, INITIAL_PAGE_SIZE);
|
||||
updateSearchParams,
|
||||
loading,
|
||||
configLoaded,
|
||||
paginationType,
|
||||
pagination,
|
||||
goToNextPage,
|
||||
goToPrevPage,
|
||||
handlePageChange,
|
||||
handlePageSizeChange
|
||||
} = useEnhancedCommonSearch("menuBannerSearch");
|
||||
|
||||
const {
|
||||
selectedRows,
|
||||
@@ -65,15 +64,10 @@ const MenuBanner = () => {
|
||||
isRowSelected
|
||||
} = useTable(dataList?.event_list || [], {mode: 'single'});
|
||||
|
||||
|
||||
const handleModalSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "regist":
|
||||
setModalType('regist');
|
||||
handleModalView('detail');
|
||||
break;
|
||||
const handleAction = async (action, item = null) => {
|
||||
switch (action) {
|
||||
case "detail":
|
||||
await MenuBannerDetailView(token, param).then(data => {
|
||||
await MenuBannerDetailView(token, item).then(data => {
|
||||
setDetailData(data.event_detail);
|
||||
setModalType('modify');
|
||||
handleModalView('detail');
|
||||
@@ -85,14 +79,13 @@ const MenuBanner = () => {
|
||||
return timeDiff < 3;
|
||||
});
|
||||
if(date_check){
|
||||
setAlertMsg(t('LAND_AUCTION_DELETE_DATE_WARNING'));
|
||||
showToast('LAND_AUCTION_DELETE_DATE_WARNING', {type: alertTypes.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');
|
||||
showModal('MENU_BANNER_SELECT_DELETE', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleAction('deleteConfirm')
|
||||
});
|
||||
break;
|
||||
case "deleteConfirm":
|
||||
let list = [];
|
||||
@@ -107,40 +100,43 @@ const MenuBanner = () => {
|
||||
});
|
||||
|
||||
if(isChecked) {
|
||||
setAlertMsg(t('LAND_AUCTION_WARNING_DELETE'))
|
||||
handleModalClose('deleteConfirm');
|
||||
showToast('LAND_AUCTION_WARNING_DELETE', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
await MenuBannerDelete(token, list).then(data => {
|
||||
handleModalClose('deleteConfirm');
|
||||
await withLoading(async () => {
|
||||
return await MenuBannerDelete(token, list);
|
||||
}).then(data => {
|
||||
if(data.result === "SUCCESS") {
|
||||
handleModalView('deleteComplete');
|
||||
showToast('DEL_COMPLETE', {type: alertTypes.success});
|
||||
// handleSearch();
|
||||
// window.location.reload();
|
||||
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){
|
||||
setAlertMsg(t('LAND_AUCTION_ERROR_DELETE_STATUS'));
|
||||
showToast('LAND_AUCTION_ERROR_DELETE_STATUS', {type: alertTypes.error});
|
||||
}else{
|
||||
setAlertMsg(t('DELETE_FAIL'));
|
||||
showToast('DELETE_FAIL', {type: alertTypes.error});
|
||||
}
|
||||
}).catch(reason => {
|
||||
setAlertMsg(t('API_FAIL'));
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
handleSearch(updateSearchParams);
|
||||
});
|
||||
|
||||
break;
|
||||
case "deleteComplete":
|
||||
handleModalClose('deleteComplete');
|
||||
window.location.reload();
|
||||
break;
|
||||
case "warning":
|
||||
setAlertMsg('')
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title>메뉴 배너 관리</Title>
|
||||
|
||||
{/* 조회조건 */}
|
||||
<FormWrapper>
|
||||
<MenuBannerSearchBar
|
||||
<CommonSearchBar
|
||||
config={config}
|
||||
searchParams={searchParams}
|
||||
onSearch={(newParams, executeSearch = true) => {
|
||||
if (executeSearch) {
|
||||
@@ -152,94 +148,49 @@ const MenuBanner = () => {
|
||||
onReset={handleReset}
|
||||
/>
|
||||
</FormWrapper>
|
||||
<ViewTableInfo total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
|
||||
{userInfo.auth_list?.some(auth => auth.id === authType.battleEventDelete) && (
|
||||
<Button theme={selectedRows.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={() => handleModalSubmit('delete')} />
|
||||
)}
|
||||
{userInfo.auth_list?.some(auth => auth.id === authType.battleEventUpdate) && (
|
||||
<Button
|
||||
theme="primary"
|
||||
text="이미지 등록"
|
||||
type="button"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
navigate('/servicemanage/menubanner/menubannerregist');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ViewTableInfo>
|
||||
<TableWrapper>
|
||||
<TableStyle ref={tableRef}>
|
||||
<caption></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40"></th>
|
||||
<th width="70">번호</th>
|
||||
<th width="80">등록 상태</th>
|
||||
<th width="150">시작일(KST)</th>
|
||||
<th width="150">종료일(KST)</th>
|
||||
<th width="300">설명 제목</th>
|
||||
<th width="90">링크여부</th>
|
||||
<th width="100">상세보기</th>
|
||||
<th width="150">히스토리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList?.list?.map(banner => (
|
||||
<tr key={banner.row_num}>
|
||||
<td>
|
||||
<CheckBox name={'select'} id={banner.id}
|
||||
setData={(e) => handleSelectRow(e, banner)}
|
||||
checked={isRowSelected(banner.id)} />
|
||||
</td>
|
||||
<td>{banner.row_num}</td>
|
||||
<StatusWapper>
|
||||
<StatusLabel $status={banner.status}>
|
||||
{opMenuBannerStatus.find(data => data.value === banner.status)?.name}
|
||||
</StatusLabel>
|
||||
</StatusWapper>
|
||||
<td>{convertKTC(banner.start_dt)}</td>
|
||||
<td>{convertKTC(banner.end_dt)}</td>
|
||||
<td>{banner.title}</td>
|
||||
<td>{opYNType.find(data => data.value === banner.is_link)?.name}</td>
|
||||
<td>
|
||||
<Button theme="line" text="상세보기"
|
||||
handleClick={e => handleModalSubmit('detail', banner.id)} />
|
||||
</td>
|
||||
<td>{banner.update_by}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</TableWrapper>
|
||||
|
||||
<Pagination postsPerPage={searchParams.pageSize} totalPosts={dataList?.total_all} setCurrentPage={handlePageChange} currentPage={searchParams.currentPage} pageLimit={INITIAL_PAGE_LIMIT} />
|
||||
{/* 조회헤더 */}
|
||||
<TableHeader
|
||||
config={tableInfo.header}
|
||||
total={dataList?.total}
|
||||
total_all={dataList?.total_all}
|
||||
handleOrderBy={handleOrderByChange}
|
||||
handlePageSize={handlePageSizeChange}
|
||||
selectedRows={selectedRows}
|
||||
onAction={handleAction}
|
||||
navigate={navigate}
|
||||
/>
|
||||
|
||||
{/*상세*/}
|
||||
<MenuBannerModal modalType={modalType} detailView={modalState.detailModal} handleDetailView={() => handleModalClose('detail')} content={detailData} setDetailData={setDetailData} />
|
||||
{/* 조회테이블 */}
|
||||
<CaliTable
|
||||
columns={tableInfo.columns}
|
||||
data={dataList?.list}
|
||||
selectedRows={selectedRows}
|
||||
onSelectRow={handleSelectRow}
|
||||
onAction={handleAction}
|
||||
refProp={tableRef}
|
||||
loading={loading}
|
||||
isRowSelected={isRowSelected}
|
||||
/>
|
||||
|
||||
{/*삭제 확인*/}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.deleteConfirmModal}
|
||||
handleCancel={() => handleModalClose('deleteConfirm')}
|
||||
handleSubmit={() => handleModalSubmit('deleteConfirm')}
|
||||
modalText={t('MENU_BANNER_SELECT_DELETE')}
|
||||
{/* 페이징 */}
|
||||
<Pagination
|
||||
postsPerPage={searchParams?.pageSize}
|
||||
totalPosts={dataList?.total_all}
|
||||
setCurrentPage={handlePageChange}
|
||||
currentPage={searchParams?.currentPage}
|
||||
pageLimit={INITIAL_PAGE_LIMIT}
|
||||
/>
|
||||
{/*삭제 완료*/}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={modalState.deleteCompleteModal}
|
||||
handleSubmit={() => handleModalSubmit('deleteComplete')}
|
||||
modalText={t('DEL_COMPLETE')}
|
||||
/>
|
||||
{/* 경고 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={alertMsg ? 'view' : 'hidden'}
|
||||
modalText={alertMsg}
|
||||
handleSubmit={() => handleModalSubmit('warning')}
|
||||
|
||||
{/* 상세 */}
|
||||
<MenuBannerModal
|
||||
modalType={modalType}
|
||||
detailView={modalState.detailModal}
|
||||
handleDetailView={() => handleModalClose('detail')}
|
||||
content={detailData}
|
||||
setDetailData={setDetailData}
|
||||
/>
|
||||
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
@@ -20,29 +20,22 @@ import {
|
||||
} from '../../styles/ModuleComponents';
|
||||
import AuthModal from '../../components/common/modal/AuthModal';
|
||||
import { authType, modalTypes } from '../../assets/data';
|
||||
import DynamicModal from '../../components/common/modal/DynamicModal';
|
||||
import { timeDiffMinute } from '../../utils';
|
||||
import { loadConfig, timeDiffMinute } from '../../utils';
|
||||
import { SingleDatePicker, SingleTimePicker } from '../../components/common';
|
||||
import CheckBox from '../../components/common/input/CheckBox';
|
||||
import ImageUploadBtn from '../../components/ServiceManage/ImageUploadBtn';
|
||||
import { useModal } from '../../hooks/hook';
|
||||
import CaliForm from '../../components/common/Custom/CaliForm';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
|
||||
const MenuBannerRegist = () => {
|
||||
const navigate = useNavigate();
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const { t } = useTranslation();
|
||||
const token = sessionStorage.getItem('token');
|
||||
const { showToast, showModal } = useAlert();
|
||||
|
||||
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('');
|
||||
@@ -50,6 +43,46 @@ const MenuBannerRegist = () => {
|
||||
const [resultData, setResultData] = useState(initData); //데이터 정보
|
||||
const [resetDateTime, setResetDateTime] = useState(false);
|
||||
|
||||
const [pageConfig, setPageConfig] = useState(null);
|
||||
const [formData, setFormData] = useState({});
|
||||
const [isFormValid, setIsFormValid] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(alertMsg){
|
||||
showToast(alertMsg, {
|
||||
type: alertTypes.error
|
||||
})
|
||||
}
|
||||
}, [alertMsg]);
|
||||
|
||||
useEffect(() => {
|
||||
const loadPageConfig = async () => {
|
||||
try {
|
||||
const config = await loadConfig('menuBannerRegist');
|
||||
setPageConfig(config);
|
||||
setFormData(config.initData);
|
||||
} catch (error) {
|
||||
console.error('Failed to load page configuration', error);
|
||||
}
|
||||
};
|
||||
|
||||
loadPageConfig();
|
||||
}, []);
|
||||
|
||||
const handleFieldValidation = (isValid, errors) => {
|
||||
setIsFormValid(isValid);
|
||||
|
||||
if (errors._form) {
|
||||
setAlertMsg(t(errors._form));
|
||||
}
|
||||
};
|
||||
|
||||
// 폼 제출 핸들러
|
||||
const handleFormSubmit = (data) => {
|
||||
setFormData(data);
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (checkCondition()) {
|
||||
setIsNullValue(false);
|
||||
@@ -189,19 +222,7 @@ const MenuBannerRegist = () => {
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "submit":
|
||||
if (!checkCondition()) return;
|
||||
const timeDiff = timeDiffMinute(resultData.start_dt, (new Date))
|
||||
if(timeDiff < 60) {
|
||||
setAlertMsg(t('EVENT_TIME_LIMIT_ADD'));
|
||||
return;
|
||||
}
|
||||
|
||||
handleModalView('registConfirm');
|
||||
break;
|
||||
case "cancel":
|
||||
|
||||
handleModalClose('cancel');
|
||||
navigate('/servicemanage/menubanner');
|
||||
break;
|
||||
case "registConfirm":
|
||||
@@ -210,12 +231,10 @@ const MenuBannerRegist = () => {
|
||||
const result = await MenuBannerSingleRegist(token, resultData);
|
||||
|
||||
setLoading(false);
|
||||
handleModalClose('registConfirm');
|
||||
handleModalView('registComplete');
|
||||
break;
|
||||
case "registComplete":
|
||||
handleModalClose('registComplete');
|
||||
|
||||
showToast('REGIST_COMPLTE', {
|
||||
type: alertTypes.success,
|
||||
duration: 4000,
|
||||
});
|
||||
navigate('/servicemanage/menubanner');
|
||||
break;
|
||||
case "warning":
|
||||
@@ -328,45 +347,27 @@ const MenuBannerRegist = () => {
|
||||
)}
|
||||
|
||||
<BtnWrapper $justify="flex-end" $gap="10px">
|
||||
<Button text="취소" theme="line" handleClick={() => handleModalView('cancel')} />
|
||||
<Button
|
||||
text="취소"
|
||||
theme="line"
|
||||
handleClick={() =>
|
||||
showModal('MENU_BANNER_REGIST_CANCEL', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleSubmit('cancel'),
|
||||
})}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
text="등록"
|
||||
theme={checkCondition() ? 'primary' : 'disable'}
|
||||
handleClick={() => handleSubmit('submit')}
|
||||
handleClick={() =>
|
||||
showModal('MENU_BANNER_REGIST_CONFIRM', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleSubmit('registConfirm'),
|
||||
})}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
|
||||
{/* 등록 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.registConfirmModal}
|
||||
modalText={t('MENU_BANNER_REGIST_CONFIRM')}
|
||||
handleSubmit={() => handleSubmit('registConfirm')}
|
||||
handleCancel={() => handleModalClose('registConfirm')}
|
||||
/>
|
||||
{/* 완료 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={modalState.registCompleteModal}
|
||||
modalText={t('REGIST_COMPLTE')}
|
||||
handleSubmit={() => handleSubmit('registComplete')}
|
||||
/>
|
||||
{/* 취소 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.confirmOkCancel}
|
||||
view={modalState.cancelModal}
|
||||
modalText={t('MENU_BANNER_REGIST_CANCEL')}
|
||||
handleCancel={() => handleModalClose('cancel')}
|
||||
handleSubmit={() => handleSubmit('cancel')}
|
||||
/>
|
||||
{/* 경고 모달 */}
|
||||
<DynamicModal
|
||||
modalType={modalTypes.completed}
|
||||
view={alertMsg ? 'view' : 'hidden'}
|
||||
modalText={alertMsg}
|
||||
handleSubmit={() => handleSubmit('warning')}
|
||||
/>
|
||||
{loading && <Loading/>}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
||||
import tableInfo from '../../assets/data/pages/userBlockTable.json'
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import useCommonSearchOld from '../../hooks/useCommonSearchOld';
|
||||
|
||||
const UserBlock = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
@@ -33,7 +34,6 @@ const UserBlock = () => {
|
||||
detail: 'hidden',
|
||||
});
|
||||
|
||||
|
||||
const {
|
||||
config,
|
||||
searchParams,
|
||||
@@ -44,8 +44,9 @@ const UserBlock = () => {
|
||||
handlePageSizeChange,
|
||||
handleOrderByChange,
|
||||
updateSearchParams,
|
||||
loading,
|
||||
configLoaded
|
||||
} = useCommonSearch(token, "userBlockSearch");
|
||||
} = useCommonSearchOld("userBlockSearch");
|
||||
|
||||
const {
|
||||
selectedRows,
|
||||
@@ -124,6 +125,8 @@ const UserBlock = () => {
|
||||
selectedRows={selectedRows}
|
||||
onSelectRow={handleSelectRow}
|
||||
onAction={handleAction}
|
||||
loading={loading}
|
||||
isRowSelected={isRowSelected}
|
||||
/>
|
||||
<Pagination
|
||||
postsPerPage={searchParams.pageSize}
|
||||
|
||||
@@ -1,507 +0,0 @@
|
||||
import { useState, useEffect, Fragment, useCallback } from 'react';
|
||||
|
||||
import { SearchBar } from '../../components/common/SearchBar';
|
||||
import CheckBox from '../../components/common/input/CheckBox';
|
||||
import Modal from '../../components/common/modal/Modal';
|
||||
import { Title, FormWrapper, TableInfo, ListTitle, ListOption, TableStyle, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import { WhiteListSearchBar } from '../../components/ServiceManage';
|
||||
|
||||
import { styled } from 'styled-components';
|
||||
import { WhiteListData, WhiteListDelete, WhiteListRegist, WhiteListAllow, WhiteListExport } from '../../apis/WhiteList';
|
||||
import { authList } from '../../store/authList';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import AuthModal from '../../components/common/modal/AuthModal';
|
||||
import { authType } from '../../assets/data';
|
||||
|
||||
const WhiteList = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const token = sessionStorage.getItem('token');
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const [resultData, setResultData] = useState({ guid: '' });
|
||||
const [doubleSubmitFlag, setDoubleSubmitFlag] = useState(false);
|
||||
|
||||
const [selectedRow, setSelectedRow] = useState([]);
|
||||
const [selectedId, setSelectedId] = useState([]);
|
||||
const [dataList, setDataList] = useState([]);
|
||||
const [deleteModalClose, setDeleteModalClose] = useState('hidden');
|
||||
const [confirmModalClose, setConfirmModalClose] = useState('hidden');
|
||||
const [deleteUserClose, setDeleteUserClose] = useState('hidden');
|
||||
const [completeModalClose, setCompleteModalClose] = useState('hidden');
|
||||
const [registModalClose, setRegistModalClose] = useState('hidden');
|
||||
const [allowModalClose, setAllowModalClose] = useState('hidden');
|
||||
const [confirmAllowClose, setConfirmAllowClose] = useState('hidden');
|
||||
const [userAllowClose, setUserAllowClose] = useState('hidden');
|
||||
const [isNullValue, setIsNullValue] = useState(false);
|
||||
const [confirmText, setConfirmText] = useState('');
|
||||
|
||||
const [approveAble, setApproveAble] = useState(false);
|
||||
|
||||
const fetchData = async () => {
|
||||
setDataList(await WhiteListData(token));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
handleApproveCheck();
|
||||
}, [selectedRow]);
|
||||
|
||||
// 전체 선택
|
||||
const handleAllSelect = () => {
|
||||
const checkAll = document.getElementById('check-all');
|
||||
let list = [];
|
||||
|
||||
if (checkAll.checked === true) {
|
||||
dataList.map((data, index) => {
|
||||
document.getElementsByName('select')[index].checked = true;
|
||||
list.push(String(data.id));
|
||||
});
|
||||
} else if (checkAll.checked === false) {
|
||||
for (let i = 0; i < dataList.length; i++) {
|
||||
dataList.map((data, index) => (document.getElementsByName('select')[index].checked = false));
|
||||
list = [];
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedRow(list);
|
||||
};
|
||||
|
||||
// 일부 선택
|
||||
const handleSelectCheckBox = e => {
|
||||
let list = [...selectedRow];
|
||||
|
||||
if (e.target.checked) {
|
||||
list.push(e.target.id);
|
||||
|
||||
setSelectedRow(list);
|
||||
} else {
|
||||
const filterList = list.filter(data => e.target.id !== data);
|
||||
setSelectedRow(filterList);
|
||||
}
|
||||
};
|
||||
|
||||
// 선택 승인 유효성 검사
|
||||
const approveCheck = e => {
|
||||
let approveList = [];
|
||||
|
||||
dataList && dataList.map(data => data.status === 'PERMITTED' && approveList.push(data.id));
|
||||
|
||||
return approveList;
|
||||
};
|
||||
|
||||
const handleApproveCheck = () => {
|
||||
let list = [];
|
||||
let approveList = approveCheck();
|
||||
|
||||
selectedRow.map(data => list.push(Number(data)));
|
||||
setApproveAble(approveList.filter(row => list.includes(row)).length === 0);
|
||||
};
|
||||
|
||||
// 선택 삭제 모달창
|
||||
const handleDeleteModalClose = () => {
|
||||
if (selectedRow.length !== 0) {
|
||||
if (deleteModalClose === 'hidden') {
|
||||
setDeleteModalClose('view');
|
||||
} else {
|
||||
setDeleteModalClose('hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 삭제 확인 모달창
|
||||
const handleConfirmModalClose = () => {
|
||||
if (confirmModalClose === 'hidden') {
|
||||
setConfirmModalClose('view');
|
||||
} else {
|
||||
setConfirmModalClose('hidden');
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// 선택 삭제 버튼
|
||||
const handleSelectedDelete = () => {
|
||||
let list = [];
|
||||
|
||||
selectedRow.map(data =>
|
||||
list.push({
|
||||
id: data,
|
||||
}),
|
||||
);
|
||||
|
||||
WhiteListDelete(token, list);
|
||||
handleDeleteModalClose();
|
||||
handleConfirmModalClose();
|
||||
};
|
||||
|
||||
// 개별 삭제 모달
|
||||
const handleUserDeleteModalClose = dataValue => {
|
||||
if (deleteUserClose === 'hidden') {
|
||||
setDeleteUserClose('view');
|
||||
} else {
|
||||
setDeleteUserClose('hidden');
|
||||
}
|
||||
|
||||
if (dataValue) {
|
||||
setSelectedId(dataValue.id);
|
||||
}
|
||||
};
|
||||
|
||||
// 개별 삭제 버튼
|
||||
const handleDelete = () => {
|
||||
let list = [];
|
||||
|
||||
list.push({ id: selectedId });
|
||||
WhiteListDelete(token, list);
|
||||
handleUserDeleteModalClose();
|
||||
handleConfirmModalClose();
|
||||
setSelectedId([]);
|
||||
};
|
||||
|
||||
// 등록 확인 모달
|
||||
const handleRegistModalClose = () => {
|
||||
if (resultData.guid.length === 0) {
|
||||
setIsNullValue(true);
|
||||
} else if (registModalClose === 'hidden') {
|
||||
setRegistModalClose('view');
|
||||
setIsNullValue(false);
|
||||
} else {
|
||||
setRegistModalClose('hidden');
|
||||
}
|
||||
};
|
||||
// console.log(resultData.guid.length)
|
||||
|
||||
// 등록 완료 모달
|
||||
const handleCompleteModalClose = () => {
|
||||
if (completeModalClose === 'hidden') {
|
||||
setCompleteModalClose('view');
|
||||
} else {
|
||||
setCompleteModalClose('hidden');
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// 화이트 리스트 등록하기
|
||||
const handleSubmit = async () => {
|
||||
const message = await WhiteListRegist(token, resultData);
|
||||
|
||||
if (message.data.data.message === '등록 하였습니다.') setConfirmText('등록이 완료되었습니다.');
|
||||
else if (message.data.data.message === 'admindb_exit_error') setConfirmText('해당 유저가 이미 등록되어있습니다. \n 확인 후 다시 이용해주세요.');
|
||||
else if (message.data.data.message === '중복된 유저 정보가 있습니다.') setConfirmText('파일 내 중복된 유저 정보가 있습니다. \n 파일을 다시 확인 후 이용해주세요.');
|
||||
else setConfirmText(message.data.data.message);
|
||||
|
||||
handleRegistModalClose();
|
||||
handleCompleteModalClose();
|
||||
};
|
||||
|
||||
// 선택 승인 모달창
|
||||
const handleAllowModalClose = () => {
|
||||
if (selectedRow.length !== 0) {
|
||||
if (allowModalClose === 'hidden') {
|
||||
setAllowModalClose('view');
|
||||
} else {
|
||||
setAllowModalClose('hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 승인 완료 모달창
|
||||
const handleConfirmAllowClose = () => {
|
||||
if (confirmAllowClose === 'hidden') {
|
||||
setConfirmAllowClose('view');
|
||||
} else {
|
||||
setConfirmModalClose('hidden');
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// 일괄 승인
|
||||
const handleAllow = () => {
|
||||
let list = [];
|
||||
|
||||
selectedRow.map(data =>
|
||||
list.push({
|
||||
id: data,
|
||||
}),
|
||||
);
|
||||
|
||||
WhiteListAllow(token, list);
|
||||
handleAllowModalClose();
|
||||
handleConfirmAllowClose();
|
||||
};
|
||||
|
||||
// 개별 승인
|
||||
const handleUserAllowModalClose = dataValue => {
|
||||
if (userAllowClose === 'hidden') {
|
||||
setUserAllowClose('view');
|
||||
} else {
|
||||
setUserAllowClose('hidden');
|
||||
}
|
||||
|
||||
if (dataValue) {
|
||||
setSelectedId(dataValue.id);
|
||||
}
|
||||
};
|
||||
|
||||
// 개별 승인 버튼
|
||||
const handleUserAllow = () => {
|
||||
let list = [];
|
||||
|
||||
list.push({ id: selectedId });
|
||||
WhiteListAllow(token, list);
|
||||
handleUserAllowModalClose();
|
||||
handleConfirmAllowClose();
|
||||
setSelectedId([]);
|
||||
};
|
||||
|
||||
// 엑셀 다운로드
|
||||
const handleXlsxExport = () => {
|
||||
const fileName = 'Caliverse_whitelist.xlsx';
|
||||
WhiteListExport(token, fileName);
|
||||
};
|
||||
|
||||
const handleCountSelectedRow = () => {
|
||||
return dataList && document.querySelectorAll('input[name="select"]:checked').length === dataList.length;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.whiteListRead) ? (
|
||||
<AuthModal />
|
||||
) : (
|
||||
<>
|
||||
<Title>화이트리스트 등록/수정</Title>
|
||||
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 21) && (
|
||||
<FormWrapper>
|
||||
<WhiteListSearchBar handleRegistModalClose={handleRegistModalClose} isNullValue={isNullValue} resultData={resultData} setResultData={setResultData} />
|
||||
</FormWrapper>
|
||||
)}
|
||||
<TableInfo>
|
||||
<ListTitle>화이트리스트 명단</ListTitle>
|
||||
<ListOption>
|
||||
<Button theme="line" type="file" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
||||
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 28) && (
|
||||
<Button theme={selectedRow.length === 0 ? 'disable' : 'line'} type="submit" text="선택 삭제" handleClick={handleDeleteModalClose} />
|
||||
)}
|
||||
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 20) && (
|
||||
<Button
|
||||
theme={selectedRow.length === 0 || !approveAble ? 'disable' : 'line'}
|
||||
type="submit"
|
||||
text="선택 승인"
|
||||
errorMessage={!approveAble}
|
||||
handleClick={handleAllowModalClose}
|
||||
/>
|
||||
)}
|
||||
</ListOption>
|
||||
</TableInfo>
|
||||
<TableWrapper>
|
||||
<TableStyle>
|
||||
<caption></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40">
|
||||
<CheckBox id="check-all" handleCheck={handleAllSelect} checked={handleCountSelectedRow()} />
|
||||
</th>
|
||||
<th width="80">번호</th>
|
||||
<th width="40%">GUID</th>
|
||||
<th width="25%">닉네임</th>
|
||||
<th width="25%">등록자(이메일주소)</th>
|
||||
<th width="100">승인</th>
|
||||
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 28) && <th width="100">삭제</th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList &&
|
||||
dataList.map(data => (
|
||||
<Fragment key={data.guid}>
|
||||
<tr>
|
||||
<td>
|
||||
<CheckBox name={'select'} id={data.id} setData={e => handleSelectCheckBox(e)} handleCheck={handleApproveCheck} />
|
||||
</td>
|
||||
<td>{data.row_num}</td>
|
||||
<td>{data.guid}</td>
|
||||
<td>{data.nickname}</td>
|
||||
<td>{data.create_by}</td>
|
||||
{/* 승인 상태 */}
|
||||
{data.status !== 'REJECT' ? (
|
||||
<td>승인</td>
|
||||
) : userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 20) ? (
|
||||
<td>
|
||||
<Button theme="line" text="승인" id={data.id} handleClick={() => handleUserAllowModalClose(data)} />
|
||||
</td>
|
||||
) : (
|
||||
<td>대기 중</td>
|
||||
)}
|
||||
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 28) && (
|
||||
<td>
|
||||
<Button theme="line" text="삭제" id={data.id} handleClick={() => handleUserDeleteModalClose(data)} />
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
</Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</TableWrapper>
|
||||
|
||||
{/* 선택삭제 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={deleteModalClose}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleDeleteModalClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
선택된 대상을 화이트리스트 유저에서
|
||||
<br />
|
||||
삭제하시겠습니까?
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleDeleteModalClose} />
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleSelectedDelete} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 등록 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={registModalClose}>
|
||||
<BtnWrapper $="flex-end">
|
||||
<ButtonClose onClick={handleRegistModalClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
화이트리스트 명단에
|
||||
<br />
|
||||
등록하시겠습니까?
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleRegistModalClose} />
|
||||
<Button
|
||||
text="확인"
|
||||
theme="primary"
|
||||
type="submit"
|
||||
size="large"
|
||||
width="100%"
|
||||
handleClick={() => {
|
||||
doubleSubmitFlag || handleSubmit();
|
||||
setDoubleSubmitFlag(true);
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 개별 삭제 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={deleteUserClose}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleUserDeleteModalClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
화이트리스트 유저를
|
||||
<br />
|
||||
삭제하시겠습니까?
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleUserDeleteModalClose} />
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleDelete} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 승인 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={allowModalClose}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleAllowModalClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
선택된 대상을 화이트리스트 유저로
|
||||
<br />
|
||||
승인하시겠습니까?
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleAllowModalClose} />
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleAllow} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 개별 승인 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={userAllowClose}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleUserAllowModalClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
선택된 대상을 화이트리스트 유저로
|
||||
<br />
|
||||
승인하시겠습니까?
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleUserAllowModalClose} />
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleUserAllow} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 완료 모달 */}
|
||||
|
||||
{/* 등록 완료 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={completeModalClose}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleCompleteModalClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">{confirmText}</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleCompleteModalClose} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 승인 완료 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={confirmAllowClose}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleConfirmAllowClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">승인이 완료되었습니다.</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleConfirmAllowClose} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 삭제 확인 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={confirmModalClose}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleConfirmModalClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">삭제가 완료되었습니다.</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleConfirmModalClose} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WhiteList;
|
||||
|
||||
const TableWrapper = styled.div`
|
||||
height: calc(100vh - 383px);
|
||||
border-top: 1px solid #000;
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
thead {
|
||||
th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StatusRed = styled.td`
|
||||
color: #d60000;
|
||||
`;
|
||||
@@ -4,7 +4,6 @@ export { default as MailRegist } from './MailRegist';
|
||||
export { default as ReportList } from './ReportList';
|
||||
export { default as UserBlock } from './UserBlock';
|
||||
export { default as UserBlockRegist } from './UserBlockRegist';
|
||||
export { default as WhiteList } from './WhiteList';
|
||||
export { default as Items } from './Items';
|
||||
export { default as Event } from './Event';
|
||||
export { default as EventRegist } from './EventRegist';
|
||||
|
||||
@@ -26,6 +26,7 @@ import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import useCommonSearchOld from '../../hooks/useCommonSearchOld';
|
||||
|
||||
const CaliumRequest = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
@@ -56,7 +57,7 @@ const CaliumRequest = () => {
|
||||
handleOrderByChange,
|
||||
updateSearchParams,
|
||||
configLoaded
|
||||
} = useCommonSearch(token, "caliumRequestSearch");
|
||||
} = useCommonSearchOld("caliumRequestSearch");
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
|
||||
@@ -145,6 +145,15 @@ export const InputLabel = styled.span`
|
||||
display: inline-block;
|
||||
min-width: fit-content;
|
||||
font-weight: 600;
|
||||
${props =>
|
||||
props.$require &&
|
||||
css`
|
||||
&:after {
|
||||
content: '*';
|
||||
color: #fe565e;
|
||||
margin-left: 5px;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export const InputItem = styled.div`
|
||||
@@ -216,6 +225,14 @@ export const ListCount = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export const HeaderPaginationContainer = styled.div`
|
||||
position: absolute;
|
||||
display: flex;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
`;
|
||||
|
||||
export const ListOption = styled.div`
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
@@ -620,3 +637,42 @@ export const TableActionButton = styled.button`
|
||||
}
|
||||
`;
|
||||
|
||||
export const SearchbarStyle = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
font-size: 14px;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ddd;
|
||||
margin: 0 0 40px;
|
||||
flex-flow: ${props => props.direction};
|
||||
gap: ${props => (props.direction === 'column' ? '20px' : '20px 0')};
|
||||
`;
|
||||
|
||||
export const SearchItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-right: 50px;
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SearchRow = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px 0;
|
||||
|
||||
&:last-child {
|
||||
border-top: 1px solid #e0e0e0;
|
||||
padding-top: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
`;
|
||||
135
src/utils/apiService.js
Normal file
135
src/utils/apiService.js
Normal file
@@ -0,0 +1,135 @@
|
||||
import { Axios } from './index';
|
||||
|
||||
/**
|
||||
* 공통 API 호출 서비스
|
||||
* @param {string} token - 인증 토큰
|
||||
* @param {string} endpointName - API 엔드포인트 이름
|
||||
* @param {Object} params - API 호출에 필요한 파라미터
|
||||
* @returns {Promise} API 호출 결과
|
||||
*/
|
||||
export const callAPI = async (apiConfig, endpointConfig, params = {}, options = {}) => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
try {
|
||||
const { method, url, dataPath } = endpointConfig;
|
||||
const baseUrl = apiConfig.baseUrl || '';
|
||||
let fullUrl = `${baseUrl}${url}`;
|
||||
let requestParams = { ...params };
|
||||
|
||||
if (options.pageField && options.pageField !== 'currentPage' && params.currentPage) {
|
||||
requestParams[options.pageField] = params.currentPage;
|
||||
delete requestParams.currentPage;
|
||||
}
|
||||
|
||||
if (options.pageSizeField && options.pageSizeField !== 'pageSize' && params.pageSize) {
|
||||
requestParams[options.pageSizeField] = params.pageSize;
|
||||
delete requestParams.pageSize;
|
||||
}
|
||||
|
||||
if (options.orderField && options.orderField !== 'orderBy' && params.orderBy) {
|
||||
requestParams[options.orderField] = params.orderBy;
|
||||
delete requestParams.orderBy;
|
||||
}
|
||||
|
||||
if (options.paginationType === 'dynamodb' && options.lastPageKeyField && 'lastEvaluatedKey' in requestParams) {
|
||||
requestParams[options.lastPageKeyField] = requestParams.lastEvaluatedKey;
|
||||
delete requestParams.lastEvaluatedKey;
|
||||
}
|
||||
|
||||
// URL 경로 파라미터 처리 (예: /users/:id)
|
||||
const pathParams = url.match(/:[a-zA-Z]+/g) || [];
|
||||
pathParams.forEach(param => {
|
||||
const paramName = param.substring(1); // ':id' -> 'id'
|
||||
if (params[paramName]) {
|
||||
fullUrl = fullUrl.replace(param, encodeURIComponent(params[paramName]));
|
||||
delete requestParams[paramName]; // URL에 사용된 파라미터는 제거
|
||||
}
|
||||
});
|
||||
|
||||
// 기본 요청 설정
|
||||
const config = {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
};
|
||||
|
||||
let response;
|
||||
|
||||
switch (method.toLowerCase()) {
|
||||
case 'get':
|
||||
// GET 요청은 URL 쿼리 파라미터로 전달
|
||||
const queryParams = new URLSearchParams();
|
||||
Object.entries(requestParams).forEach(([key, value]) => {
|
||||
queryParams.append(key, value);
|
||||
});
|
||||
const queryString = queryParams.toString();
|
||||
response = await Axios.get(
|
||||
`${fullUrl}${queryString ? '?' + queryString : ''}`,
|
||||
config
|
||||
);
|
||||
break;
|
||||
|
||||
case 'post':
|
||||
response = await Axios.post(fullUrl, requestParams, config);
|
||||
break;
|
||||
|
||||
case 'put':
|
||||
response = await Axios.put(fullUrl, requestParams, config);
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
response = await Axios.delete(fullUrl, {
|
||||
...config,
|
||||
data: requestParams
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported method: ${method}`);
|
||||
}
|
||||
|
||||
// 데이터 경로에 따라 결과 반환
|
||||
if (dataPath) {
|
||||
return dataPath.split('.').reduce((obj, path) => obj && obj[path], response);
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`API Call Error for ${apiConfig.baseUrl}${endpointConfig.url}:`, error);
|
||||
if (error instanceof Error) {
|
||||
throw new Error(`${endpointConfig.name || 'API'} Error: ${error.message}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* API 설정에서 엔드포인트 가져오기
|
||||
* @param {Object} apiConfig - API 설정 객체
|
||||
* @param {string} endpointName - 엔드포인트 이름
|
||||
* @returns {Object} 엔드포인트 설정
|
||||
*/
|
||||
export const getEndpoint = (apiConfig, endpointName) => {
|
||||
const endpoint = apiConfig.endpoints[endpointName];
|
||||
if (!endpoint) {
|
||||
throw new Error(`Endpoint not found: ${endpointName}`);
|
||||
}
|
||||
return endpoint;
|
||||
};
|
||||
|
||||
/**
|
||||
* API 모듈 생성
|
||||
* @param {Object} apiConfig - API 설정 객체
|
||||
* @returns {Object} API 함수들을 포함한 객체
|
||||
*/
|
||||
export const createAPIModule = (apiConfig) => {
|
||||
const apiModule = {};
|
||||
|
||||
Object.keys(apiConfig.endpoints).forEach(endpointName => {
|
||||
const endpoint = apiConfig.endpoints[endpointName];
|
||||
|
||||
apiModule[endpointName] = async (token, params = {}, options = {}) => {
|
||||
// 단일 파라미터인 경우 처리
|
||||
const processedParams = typeof params !== 'object' ? { id: params } : params;
|
||||
return callAPI(token, apiConfig, endpoint, processedParams, options);
|
||||
};
|
||||
});
|
||||
|
||||
return apiModule;
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import * as optionsConfig from '../assets/data/options';
|
||||
|
||||
export const convertKTC = (dt, nation = true) => {
|
||||
if (!dt) return "";
|
||||
if (typeof dt !== "string") return "";
|
||||
@@ -54,3 +56,21 @@ export const truncateText = (text) => {
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
export const getOptionsArray = (optionsKey) => {
|
||||
if (typeof optionsKey === 'string') {
|
||||
return optionsConfig[optionsKey] || [];
|
||||
}
|
||||
return optionsKey || [];
|
||||
};
|
||||
|
||||
export const loadConfig = async (configPath) => {
|
||||
try {
|
||||
// 동적 import
|
||||
const module = await import(`../assets/data/pages/${configPath}.json`);
|
||||
return module.default;
|
||||
} catch (error) {
|
||||
console.error(`Failed to load search configuration: ${configPath}`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user