선택 드랍다운 넓이 수정
아이템 백과사전 추가
This commit is contained in:
@@ -28,6 +28,7 @@ import {
|
||||
BattleEvent,
|
||||
MenuBanner, MenuBannerRegist,
|
||||
} from './pages/ServiceManage';
|
||||
import MetaItemView from './pages/DataManage/MetaItemView';
|
||||
|
||||
const RouteInfo = () => {
|
||||
return (
|
||||
@@ -61,6 +62,7 @@ const RouteInfo = () => {
|
||||
<Route path="gamelogview" element={<GameLogView />} />
|
||||
<Route path="cryptview" element={<CryptView />} />
|
||||
<Route path="businesslogview" element={<BusinessLogView />} />
|
||||
<Route path="itemdictionary" element={<MetaItemView />} />
|
||||
</Route>
|
||||
<Route path="/servicemanage">
|
||||
<Route path="board" element={<Board />} />
|
||||
|
||||
39
src/apis/Dictionary.js
Normal file
39
src/apis/Dictionary.js
Normal file
@@ -0,0 +1,39 @@
|
||||
//운영 정보 관리 - 백과사전 api 연결
|
||||
|
||||
import { Axios } from '../utils';
|
||||
|
||||
// 아이템 백과사전 조회
|
||||
export const getItemDictionaryList = async (token, searchType, searchData, largeType, smallType, brand, gender, order, size, currentPage) => {
|
||||
try {
|
||||
const response = await Axios.get(`/api/v1/dictionary/item/list?search_type=${searchType}&search_data=${searchData}
|
||||
&large_type=${largeType}&small_type=${smallType}&brand=${brand}&gender=${gender}
|
||||
&orderby=${order}&page_no=${currentPage}&page_size=${size}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('getItemDictionaryList API error:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const BrandView = async (token) => {
|
||||
try {
|
||||
const res = await Axios.get(
|
||||
`/api/v1/dictionary/brand/list`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
|
||||
return res.data.data.brand_list;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('BrandView Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -123,7 +123,7 @@ export const STATUS_STYLES = {
|
||||
},
|
||||
};
|
||||
|
||||
export const logFieldLabels = {
|
||||
export const FieldLabels = {
|
||||
// DynamoDB 필드
|
||||
'attribFieldName': '속성 명',
|
||||
'pk': '파티션 키',
|
||||
@@ -181,6 +181,28 @@ export const logFieldLabels = {
|
||||
'ffa_hot_time': 'FFA 핫타임',
|
||||
'round_count': '라운드 수',
|
||||
|
||||
//dictionary
|
||||
'max_count': '최대 보유 가능 수량',
|
||||
'stack_max_count': '최대 스택 가능 수량',
|
||||
'expire_type': '아이템 만료 타입',
|
||||
'expire_start_dt': '만료 시작 시간',
|
||||
'expire_end_dt': '만료 종료 시간',
|
||||
'expire_time_sec': '만료 시간 연장 여부',
|
||||
'user_tradable': '유저 간 거래 가능 여부',
|
||||
'system_tradable': '상점에서 판매 가능 여부',
|
||||
'throwable': '버리기 가능 여부',
|
||||
'cart_buy': '상점에서 구매 가능 여부',
|
||||
'rarity': '희귀도',
|
||||
'default_attrib': '기본 속성',
|
||||
'attrib_random_group': '랜덤 그룹',
|
||||
'item_set': '아이템 세트',
|
||||
'buff': '아이템 사용 시 획득 버프',
|
||||
'dress_slot_type': '착용 부위',
|
||||
'product_link': '제품 URL',
|
||||
'prop_small_type': '제작 아이템 그룹',
|
||||
'gacha_group_id': '랜덤박스 그룹 ID',
|
||||
'ugq_action': 'UGQ 사용 가능 여부',
|
||||
'linked_land': '연결된 랜드 ID',
|
||||
};
|
||||
|
||||
export const historyTables = {
|
||||
|
||||
@@ -118,6 +118,14 @@ export const menuConfig = {
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
itemdictionary: {
|
||||
title: '아이템 백과사전 조회',
|
||||
permissions: {
|
||||
read: authType.businessLogRead
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -154,16 +162,16 @@ export const menuConfig = {
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
reportlist: {
|
||||
title: '신고내역',
|
||||
permissions: {
|
||||
read: authType.reportRead,
|
||||
update: authType.reportUpdate,
|
||||
delete: authType.reportDelete
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
// reportlist: {
|
||||
// title: '신고내역',
|
||||
// permissions: {
|
||||
// read: authType.reportRead,
|
||||
// update: authType.reportUpdate,
|
||||
// delete: authType.reportDelete
|
||||
// },
|
||||
// view: true,
|
||||
// authLevel: adminAuthLevel.NONE
|
||||
// },
|
||||
event: {
|
||||
title: '보상 이벤트 관리',
|
||||
permissions: {
|
||||
|
||||
@@ -220,6 +220,11 @@ export const landSearchType = [
|
||||
{ value: 'NAME', name: '랜드명' },
|
||||
];
|
||||
|
||||
export const itemSearchType = [
|
||||
{ value: 'ID', name: '아이템ID' },
|
||||
{ value: 'NAME', name: '아이템명' },
|
||||
];
|
||||
|
||||
export const blockType = [
|
||||
{ value: '', name: '선택' },
|
||||
{ value: 'Access_Restrictions', name: '접근 제한' },
|
||||
@@ -388,7 +393,7 @@ export const opEquipType = [
|
||||
{ value: 3, name: '타투장착' },
|
||||
]
|
||||
|
||||
export const opItemType = [
|
||||
export const opItemLargeType = [
|
||||
{ value: 'TOOL', name: '도구' },
|
||||
{ value: 'EXPENDABLE', name: '소모품' },
|
||||
{ value: 'TICKET', name: '티켓' },
|
||||
@@ -400,6 +405,95 @@ export const opItemType = [
|
||||
{ value: 'CURRENCY', name: '재화' },
|
||||
{ value: 'PRODUCT', name: '제품' },
|
||||
{ value: 'BEAUTY', name: '뷰티' },
|
||||
{ value: 'SET_BOX', name: '세트 박스' },
|
||||
]
|
||||
|
||||
export const opItemSmallType = [
|
||||
{ value: 'HANDMIRROR', name: '손거울' },
|
||||
{ value: 'LIGHTSTICK', name: '응원봉' },
|
||||
{ value: 'FIRECRACKER', name: '폭죽총' },
|
||||
{ value: 'LIGHTSABER', name: '광선검' },
|
||||
{ value: 'REGISTER_ITEM_SOCIAL_ACTION', name: '소셜액션 등록형 아이템' },
|
||||
{ value: 'REGISTER_ITEM_INTERIOR', name: '인테리어 등록형 아이템' },
|
||||
{ value: 'SAIYAN_AURA', name: '초사이어인 오라' },
|
||||
{ value: 'TICKET', name: '소모형 티켓' },
|
||||
{ value: 'RANDOMBOX', name: '골드(재화) 가챠 랜덤 박스' },
|
||||
{ value: 'SHIRT', name: '상의' },
|
||||
{ value: 'DRESS', name: '드레스' },
|
||||
{ value: 'OUTER', name: '겉옷' },
|
||||
{ value: 'PANTS', name: '하의' },
|
||||
{ value: 'GLOVES', name: '장갑' },
|
||||
{ value: 'RING', name: '반지' },
|
||||
{ value: 'BRACELET', name: '팔찌' },
|
||||
{ value: 'BAG', name: '가방(크로스백)' },
|
||||
{ value: 'BACKPACK', name: '가방(백팩)' },
|
||||
{ value: 'CAP', name: '모자' },
|
||||
{ value: 'MASK', name: '가면' },
|
||||
{ value: 'GLASSES', name: '안경' },
|
||||
{ value: 'EARRING', name: '귀걸이' },
|
||||
{ value: 'NECKLACE', name: '목걸이' },
|
||||
{ value: 'SHOES', name: '신발' },
|
||||
{ value: 'SOCKS', name: '양말' },
|
||||
{ value: 'ANKLET', name: '발찌' },
|
||||
{ value: 'OFFICECHAIR', name: '의자' },
|
||||
{ value: 'WALLMOUNTTV', name: '벽걸이TV' },
|
||||
{ value: 'OUTDOORCHAIR', name: '야외용의자' },
|
||||
{ value: 'TV', name: 'TV' },
|
||||
{ value: 'VIGNETTE', name: '소품' },
|
||||
{ value: 'COOKWARE', name: '조리도구' },
|
||||
{ value: 'KITCHEN_TOOL', name: '부엌도구' },
|
||||
{ value: 'LAPTOP', name: '노트북' },
|
||||
{ value: 'OUTDOOR_GOODS', name: '캠핑도구' },
|
||||
{ value: 'BED', name: '침대' },
|
||||
{ value: 'DECO', name: '소형장식품' },
|
||||
{ value: 'FURNITURE', name: '(러그)가구' },
|
||||
{ value: 'MUSIC', name: '악기/음향' },
|
||||
{ value: 'SHELF_S', name: '소형 선반(TV다이)' },
|
||||
{ value: 'SHELF_L', name: '대형 선반(금고,책장)' },
|
||||
{ value: 'SOFA_SINGLE', name: '1인용 소파' },
|
||||
{ value: 'SOFA_COUCH', name: '카우치 소파' },
|
||||
{ value: 'LIGHT_CEILING', name: '천정 조명' },
|
||||
{ value: 'LIGHT_FLOOR', name: '스탠드 조명' },
|
||||
{ value: 'LIGHT_TABLE', name: '탁상 조명' },
|
||||
{ value: 'LIGHT_PENDENT', name: '팬던트 조명' },
|
||||
{ value: 'TABLE_S', name: '소형 테이블' },
|
||||
{ value: 'TABLE_L', name: '대형 테이블' },
|
||||
{ value: 'TABLE_LIVINGROOM', name: '거실 테이블' },
|
||||
{ value: 'TABLE_OFFICE', name: '사무용 테이블' },
|
||||
{ value: 'LEISURE_APPLIANCE', name: '스포츠/여가' },
|
||||
{ value: 'INDUCTION', name: '인덕션' },
|
||||
{ value: 'MICROWAVE', name: '전자레인지' },
|
||||
{ value: 'LARGE_APPLIANCE', name: '대형가전' },
|
||||
{ value: 'COSMETIC', name: '화장품' },
|
||||
{ value: 'CHEST', name: '앞면' },
|
||||
{ value: 'LEFT_ARM', name: '왼팔' },
|
||||
{ value: 'RIGHT_ARM', name: '오른팔' },
|
||||
{ value: 'BACK', name: '후면' },
|
||||
{ value: 'LEFT_LEG', name: '왼다리' },
|
||||
{ value: 'RIGHT_LEG', name: '오른다리' },
|
||||
{ value: 'CARTRIDGE', name: '속성 카트리지' },
|
||||
{ value: 'BUFF_DRINK', name: '드링크(물약) 아이템' },
|
||||
{ value: 'INTERPHONE', name: '인터폰' },
|
||||
{ value: 'MEGAPHONE', name: '확성기' },
|
||||
{ value: 'CURRENCY', name: 'CURRENCY' },
|
||||
{ value: 'NFTLAND', name: 'NFTLAND' },
|
||||
{ value: 'SUMMONSTONE', name: '소환석' },
|
||||
{ value: 'GOLD', name: '골드' },
|
||||
{ value: 'SAPPHIRE', name: '사파이어' },
|
||||
{ value: 'CALIUM', name: '칼리움' },
|
||||
{ value: 'BEAM', name: '빔' },
|
||||
{ value: 'RUBY', name: '루비' },
|
||||
{ value: 'LIGHT_LIMITED', name: '언리얼 라이트 사용 조명' },
|
||||
{ value: 'SPEAKER', name: '재생 기능성 스피커' },
|
||||
{ value: 'SETBOX', name: '세트박스' },
|
||||
{ value: 'DRESS_SHOES', name: '드레스+신발' },
|
||||
{ value: 'SHOULDERBAG', name: '숄더백' },
|
||||
{ value: 'RECIPE', name: '레시피' }
|
||||
]
|
||||
|
||||
export const opGender = [
|
||||
{ value: 'MALE', name: '남성' },
|
||||
{ value: 'FEMALE', name: '여성' },
|
||||
]
|
||||
|
||||
export const opHistoryType = [
|
||||
@@ -820,6 +914,7 @@ export const opCommonStatus = [
|
||||
|
||||
export const logAction = [
|
||||
{ value: "None", name: "전체" },
|
||||
{ value: "AdminToolQuestTaskForceComplete", name: "AdminToolQuestTaskForceComplete" },
|
||||
{ value: "AIChatDeleteCharacter", name: "AIChatDeleteCharacter" },
|
||||
{ value: "AIChatDeleteUser", name: "AIChatDeleteUser" },
|
||||
{ value: "AIChatGetCharacter", name: "AIChatGetCharacter" },
|
||||
@@ -854,12 +949,12 @@ export const logAction = [
|
||||
{ value: "BeaconShopUpdateDailyCount", name: "BeaconShopUpdateDailyCount" },
|
||||
{ value: "BeaconShopDeleteRecord", name: "BeaconShopDeleteRecord" },
|
||||
{ value: "BeaconShopDeactiveItems", name: "BeaconShopDeactiveItems" },
|
||||
{ value: "BrokerApiAdmin", name: "BrokerApiAdmin" },
|
||||
// { value: "BrokerApiAdmin", name: "BrokerApiAdmin" },
|
||||
{ value: "BrokerApiPlanetAuth", name: "BrokerApiPlanetAuth" },
|
||||
{ value: "BrokerApiUserExchangeOrderCompleted", name: "BrokerApiUserExchangeOrderCompleted" },
|
||||
{ value: "BrokerApiUserExchangeOrderCreated", name: "BrokerApiUserExchangeOrderCreated" },
|
||||
{ value: "BrokerApiUserSystemMailSend", name: "BrokerApiUserSystemMailSend" },
|
||||
{ value: "BrokerApiUserEchoSystemRequest", name: "BrokerApiUserEchoSystemRequest" },
|
||||
// { value: "BrokerApiUserSystemMailSend", name: "BrokerApiUserSystemMailSend" },
|
||||
// { value: "BrokerApiUserEchoSystemRequest", name: "BrokerApiUserEchoSystemRequest" },
|
||||
{ value: "BrokerApiUserLogin", name: "BrokerApiUserLogin" },
|
||||
{ value: "BuffAdd", name: "BuffAdd" },
|
||||
{ value: "BuffDelete", name: "BuffDelete" },
|
||||
@@ -909,6 +1004,7 @@ export const logAction = [
|
||||
{ value: "ClaimReward", name: "ClaimReward" },
|
||||
{ value: "ConvertCalium", name: "ConvertCalium" },
|
||||
{ value: "ConvertExchangeCalium", name: "ConvertExchangeCalium" },
|
||||
{ value: "ContentsMove", name: "ContentsMove" },
|
||||
{ value: "CraftFinish", name: "CraftFinish" },
|
||||
{ value: "CraftHelp", name: "CraftHelp" },
|
||||
{ value: "CraftRecipeRegister", name: "CraftRecipeRegister" },
|
||||
@@ -935,6 +1031,12 @@ export const logAction = [
|
||||
{ value: "FriendAdd", name: "FriendAdd" },
|
||||
{ value: "FriendDelete", name: "FriendDelete" },
|
||||
{ value: "GainLandProfit", name: "GainLandProfit" },
|
||||
{ value: "GameModeObjectInteraction", name: "GameModeObjectInteraction" },
|
||||
{ value: "GameModeObjectStateUpdate", name: "GameModeObjectStateUpdate" },
|
||||
{ value: "GameModeUserDead", name: "GameModeUserDead" },
|
||||
{ value: "GameModeUserRespawn", name: "GameModeUserRespawn" },
|
||||
{ value: "GameModePenalty", name: "GameModePenalty" },
|
||||
{ value: "GameModeAddMatchCount", name: "GameModeAddMatchCount" },
|
||||
{ value: "InviteParty", name: "InviteParty" },
|
||||
{ value: "ItemBuy", name: "ItemBuy" },
|
||||
{ value: "ItemDestroy", name: "ItemDestroy" },
|
||||
@@ -965,8 +1067,17 @@ export const logAction = [
|
||||
{ value: "MailRead", name: "MailRead" },
|
||||
{ value: "MailSend", name: "MailSend" },
|
||||
{ value: "MailTaken", name: "MailTaken" },
|
||||
{ value: "MatchReserve", name: "MatchReserve" },
|
||||
{ value: "MatchCancel", name: "MatchCancel" },
|
||||
{ value: "MatchResult", name: "MatchResult" },
|
||||
{ value: "MatchRoomUserJoin", name: "MatchRoomUserJoin" },
|
||||
{ value: "MatchRoomUserQuit", name: "MatchRoomUserQuit" },
|
||||
{ value: "MatchRoomCreate", name: "MatchRoomCreate" },
|
||||
{ value: "MatchRoomUpdate", name: "MatchRoomUpdate" },
|
||||
{ value: "MatchRoomDestroy", name: "MatchRoomDestroy" },
|
||||
{ value: "ModifyLandInfo", name: "ModifyLandInfo" },
|
||||
{ value: "MoneyChange", name: "MoneyChange" },
|
||||
{ value: "MoveToBeacon", name: "MoveToBeacon" },
|
||||
{ value: "ProductGive", name: "ProductGive" },
|
||||
{ value: "ProductOpenFailed", name: "ProductOpenFailed" },
|
||||
{ value: "ProductOpenSuccess", name: "ProductOpenSuccess" },
|
||||
@@ -990,6 +1101,11 @@ export const logAction = [
|
||||
{ value: "ReplySummonParty", name: "ReplySummonParty" },
|
||||
{ value: "ReservationEnterToServer", name: "ReservationEnterToServer" },
|
||||
{ value: "RewardProp", name: "RewardProp" },
|
||||
{ value: "RunRaceFinishReward", name: "RunRaceFinishReward" },
|
||||
{ value: "RunRaceRespawnReward", name: "RunRaceRespawnReward" },
|
||||
{ value: "RunRaceUnFinishReward", name: "RunRaceUnFinishReward" },
|
||||
{ value: "RunRaceCheckPointAbusing", name: "RunRaceCheckPointAbusing" },
|
||||
{ value: "RunRaceResultSummary", name: "RunRaceResultSummary" },
|
||||
{ value: "SaveMyhome", name: "SaveMyhome" },
|
||||
{ value: "SeasonPassBuyCharged", name: "SeasonPassBuyCharged" },
|
||||
{ value: "SeasonPassStartNew", name: "SeasonPassStartNew" },
|
||||
@@ -1039,6 +1155,7 @@ export const logAction = [
|
||||
{ value: "UpdateGameOption", name: "UpdateGameOption" },
|
||||
{ value: "UpdateLanguage", name: "UpdateLanguage" },
|
||||
{ value: "UpdateUgcNpcLike", name: "UpdateUgcNpcLike" },
|
||||
{ value: "UpdateGameModePlayerRegulation", name: "UpdateGameModePlayerRegulation" },
|
||||
{ value: "UserBlock", name: "UserBlock" },
|
||||
{ value: "UserBlockCancel", name: "UserBlockCancel" },
|
||||
{ value: "UserCreate", name: "UserCreate" },
|
||||
@@ -1091,6 +1208,9 @@ export const logDomain = [
|
||||
{ value: "Farming", name: "Farming" },
|
||||
{ value: "FarmingReward", name: "FarmingReward" },
|
||||
{ value: "GameLogInOut", name: "GameLogInOut" },
|
||||
{ value: "GameObjectInteraction", name: "GameObjectInteraction" },
|
||||
{ value: "GameModePenalty", name: "GameModePenalty" },
|
||||
{ value: "GameModePlayRegulation", name: "GameModePlayRegulation" },
|
||||
{ value: "Item", name: "Item" },
|
||||
{ value: "IgmApi", name: "IgmApi" },
|
||||
{ value: "MyHome", name: "MyHome" },
|
||||
@@ -1102,6 +1222,9 @@ export const logDomain = [
|
||||
{ value: "Mail", name: "Mail" },
|
||||
{ value: "MailStoragePeriodExpired", name: "MailStoragePeriodExpired" },
|
||||
{ value: "MailProfile", name: "MailProfile" },
|
||||
{ value: "MatchUser", name: "MatchUser" },
|
||||
{ value: "MatchServerUser", name: "MatchServerUser" },
|
||||
{ value: "MatchRoom", name: "MatchRoom" },
|
||||
{ value: "Party", name: "Party" },
|
||||
{ value: "PartyMember", name: "PartyMember" },
|
||||
{ value: "PartyVote", name: "PartyVote" },
|
||||
@@ -1119,6 +1242,11 @@ export const logDomain = [
|
||||
{ value: "RenewalShopProducts", name: "RenewalShopProducts" },
|
||||
{ value: "Rental", name: "Rental" },
|
||||
{ value: "RewardProp", name: "RewardProp" },
|
||||
{ value: "RunRaceFinishReward", name: "RunRaceFinishReward" },
|
||||
{ value: "RunRaceRespawnReward", name: "RunRaceRespawnReward" },
|
||||
{ value: "RunRaceUnFinishReward", name: "RunRaceUnFinishReward" },
|
||||
{ value: "RunRaceCheckPointAbusing", name: "RunRaceCheckPointAbusing" },
|
||||
{ value: "RunRaceRewardSummary", name: "RunRaceRewardSummary" },
|
||||
{ value: "Stage", name: "Stage" },
|
||||
{ value: "SocialAction", name: "SocialAction" },
|
||||
{ value: "SeasonPass", name: "SeasonPass" },
|
||||
|
||||
@@ -52,6 +52,7 @@ export const authType = {
|
||||
menuBannerRead: 50,
|
||||
menuBannerUpdate: 51,
|
||||
menuBannerDelete: 52,
|
||||
itemDictionaryRead: 53,
|
||||
|
||||
|
||||
levelReader: 999,
|
||||
|
||||
@@ -125,6 +125,7 @@ const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 }
|
||||
value={currentValue}
|
||||
onChange={(value) => onChange(key, value, handler)}
|
||||
placeholder={placeholder || `${label} 선택`}
|
||||
popupMatchSelectWidth={false}
|
||||
>
|
||||
{options && options.map((option) => (
|
||||
<Select.Option key={option.value} value={option.value}>
|
||||
|
||||
55
src/components/common/Layout/DetailInfo.js
Normal file
55
src/components/common/Layout/DetailInfo.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { Card, Descriptions } from 'antd';
|
||||
import { getFieldLabel } from '../../../utils';
|
||||
|
||||
const InfoCard = ({
|
||||
title,
|
||||
data,
|
||||
keyPrefix = 'item',
|
||||
size = 'small',
|
||||
column = 1,
|
||||
bordered = true,
|
||||
type = 'inner'
|
||||
}) => {
|
||||
|
||||
if (!data ||
|
||||
typeof data !== 'object' ||
|
||||
Object.keys(data).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const items = Object.entries(data).map(([key, value]) => ({
|
||||
key: `${keyPrefix}-${key}`,
|
||||
label: getFieldLabel(key),
|
||||
children: (() => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-';
|
||||
}
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.join(', ');
|
||||
}
|
||||
return JSON.stringify(value, null, 2);
|
||||
}
|
||||
return String(value);
|
||||
})()
|
||||
}));
|
||||
|
||||
return (
|
||||
<Card
|
||||
size={size}
|
||||
title={title}
|
||||
type={type}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
<Descriptions
|
||||
bordered={bordered}
|
||||
column={column}
|
||||
size={size}
|
||||
items={items}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoCard;
|
||||
@@ -4,5 +4,6 @@ import MainLayout from './MainLayout';
|
||||
import AnimatedPageWrapper from './AnimatedPageWrapper';
|
||||
import DetailGrid from './DetailGrid';
|
||||
import DetailLayout from './DetailLayout';
|
||||
import InfoCard from './DetailInfo'
|
||||
|
||||
export { Layout, LoginLayout, MainLayout, AnimatedPageWrapper, DetailGrid, DetailLayout };
|
||||
export { Layout, LoginLayout, MainLayout, AnimatedPageWrapper, DetailGrid, DetailLayout, InfoCard };
|
||||
|
||||
@@ -1,43 +1,79 @@
|
||||
import React from 'react';
|
||||
import { Tabs } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
// 통합된 애니메이션 탭 컴포넌트
|
||||
const AnimatedTabs = ({ items, activeKey, onChange }) => {
|
||||
const AnimatedTabs = ({ items, activeKey, onChange, tabPosition = 'center' }) => {
|
||||
// 각 항목의 children을 애니메이션 래퍼로 감싸기
|
||||
const tabItems = items.map(item => ({
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
children: (
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={activeKey}
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 300,
|
||||
damping: 30
|
||||
}}
|
||||
>
|
||||
<AnimatedContent key={`content-${item.key}`}>
|
||||
{item.children}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</AnimatedContent>
|
||||
)
|
||||
|
||||
// children: (
|
||||
// <AnimatePresence mode="wait">
|
||||
// <motion.div
|
||||
// key={activeKey}
|
||||
// initial={{ opacity: 0, x: 50 }}
|
||||
// animate={{ opacity: 1, x: 0 }}
|
||||
// exit={{ opacity: 0, x: -50 }}
|
||||
// transition={{
|
||||
// type: "spring",
|
||||
// stiffness: 300,
|
||||
// damping: 30
|
||||
// }}
|
||||
// >
|
||||
// {item.children}
|
||||
// </motion.div>
|
||||
// </AnimatePresence>
|
||||
// )
|
||||
}));
|
||||
|
||||
return (
|
||||
<StyledTabs
|
||||
type="card"
|
||||
activeKey={activeKey}
|
||||
onChange={onChange}
|
||||
centered={true}
|
||||
centered={tabPosition === 'center'}
|
||||
items={tabItems}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const slideInRight = keyframes`
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
`;
|
||||
|
||||
const fadeIn = keyframes`
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
const AnimatedContent = styled.div`
|
||||
animation: ${slideInRight} 0.3s ease-out;
|
||||
|
||||
/* 대안으로 더 부드러운 페이드 인 효과 */
|
||||
/* animation: ${fadeIn} 0.4s ease-out; */
|
||||
`;
|
||||
|
||||
|
||||
// const AnimatedTabs = ({ items, activeKey, onChange }) => {
|
||||
// return (
|
||||
// <StyledTabs
|
||||
@@ -76,16 +112,12 @@ const StyledTabs = styled(Tabs)`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
//align-items: center;
|
||||
|
||||
.ant-tabs-nav {
|
||||
margin-bottom: 16px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.ant-tabs-nav-wrap {
|
||||
justify-content: center;
|
||||
}
|
||||
//.ant-tabs-nav {
|
||||
// margin-bottom: 16px;
|
||||
// width: 80%;
|
||||
//}
|
||||
|
||||
.ant-tabs-tab {
|
||||
padding: 8px 16px;
|
||||
|
||||
217
src/components/searchBar/ItemDictionarySearchBar.js
Normal file
217
src/components/searchBar/ItemDictionarySearchBar.js
Normal file
@@ -0,0 +1,217 @@
|
||||
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
itemSearchType,
|
||||
logAction,
|
||||
logDomain, opGender,
|
||||
opItemLargeType,
|
||||
opItemSmallType,
|
||||
userSearchType2,
|
||||
} from '../../assets/data/options';
|
||||
import { BusinessLogList } from '../../apis/Log';
|
||||
import {SearchFilter} from '../ServiceManage';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import { getItemDictionaryList } from '../../apis';
|
||||
|
||||
export const useItemDictionarySearch = (token, initialPageSize) => {
|
||||
const {showToast} = useAlert();
|
||||
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
search_type: 'ID',
|
||||
search_data: '',
|
||||
large_type: 'ALL',
|
||||
small_type: 'ALL',
|
||||
brand: 'ALL',
|
||||
gender: 'ALL',
|
||||
order_by: 'ASC',
|
||||
page_size: initialPageSize,
|
||||
page_no: 1
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
//초기 데이터 로드 안함
|
||||
// const initialLoad = async () => {
|
||||
// await fetchData(searchParams);
|
||||
// };
|
||||
|
||||
// initialLoad();
|
||||
}, [token]);
|
||||
|
||||
const fetchData = useCallback(async (params) => {
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const result = await getItemDictionaryList(
|
||||
token,
|
||||
params.search_type,
|
||||
params.search_data,
|
||||
params.large_type,
|
||||
params.small_type,
|
||||
params.brand,
|
||||
params.gender,
|
||||
params.order_by,
|
||||
params.page_size,
|
||||
params.page_no
|
||||
);
|
||||
if(result.result === "ERROR"){
|
||||
showToast(result.result, {type: alertTypes.error});
|
||||
}
|
||||
setData(result.data);
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
showToast('error', {type: alertTypes.error});
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const updateSearchParams = useCallback((newParams) => {
|
||||
setSearchParams(prev => ({
|
||||
...prev,
|
||||
...newParams
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleSearch = useCallback(async (newParams = {}, executeSearch = true) => {
|
||||
const updatedParams = {
|
||||
...searchParams,
|
||||
...newParams,
|
||||
page_no: newParams.page_no || 1 // Reset to first page on new search
|
||||
};
|
||||
updateSearchParams(updatedParams);
|
||||
|
||||
if (executeSearch) {
|
||||
return await fetchData(updatedParams);
|
||||
}
|
||||
return null;
|
||||
}, [searchParams, fetchData]);
|
||||
|
||||
const handleReset = useCallback(async () => {
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() - 1);
|
||||
const resetParams = {
|
||||
search_type: 'ID',
|
||||
search_data: '',
|
||||
large_type: 'ALL',
|
||||
small_type: 'ALL',
|
||||
brand: 'ALL',
|
||||
gender: 'ALL',
|
||||
order_by: 'ASC',
|
||||
page_size: initialPageSize,
|
||||
page_no: 1
|
||||
};
|
||||
setSearchParams(resetParams);
|
||||
return await fetchData(resetParams);
|
||||
}, [initialPageSize, fetchData]);
|
||||
|
||||
const handlePageChange = useCallback(async (newPage) => {
|
||||
return await handleSearch({ page_no: newPage }, true);
|
||||
}, [handleSearch]);
|
||||
|
||||
const handlePageSizeChange = useCallback(async (newSize) => {
|
||||
return await handleSearch({ page_size: newSize, page_no: 1 }, true);
|
||||
}, [handleSearch]);
|
||||
|
||||
const handleOrderByChange = useCallback(async (newOrder) => {
|
||||
return await handleSearch({ order_by: newOrder }, true);
|
||||
}, [handleSearch]);
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
loading,
|
||||
data,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
handleOrderByChange,
|
||||
updateSearchParams
|
||||
};
|
||||
};
|
||||
|
||||
const ItemDictionarySearchBar = ({ searchParams, onSearch, onReset, brandData }) => {
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
onSearch(searchParams, true);
|
||||
};
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchParams.search_type} onChange={e => onSearch({search_type: e.target.value }, false)}>
|
||||
{itemSearchType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchParams.search_type === 'ID' ? '아이템 ID 입력' : '아이템명 입력'}
|
||||
value={searchParams.search_data}
|
||||
width="260px"
|
||||
onChange={e => onSearch({ search_data: e.target.value }, false)}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>대분류</InputLabel>
|
||||
<SelectInput value={searchParams.large_type} onChange={e => onSearch({ large_type: e.target.value }, false)} >
|
||||
<option value='ALL'>전체</option>
|
||||
{opItemLargeType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>소분류</InputLabel>
|
||||
<SelectInput value={searchParams.small_type} onChange={e => onSearch({ small_type: e.target.value }, false)} >
|
||||
<option value='ALL'>전체</option>
|
||||
{opItemSmallType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<InputLabel>브랜드</InputLabel>
|
||||
<SelectInput value={searchParams.brand} onChange={e => onSearch({ brand: e.target.value })}>
|
||||
<option value='ALL'>전체</option>
|
||||
{brandData?.map((data, index) => (
|
||||
<option key={index} value={data.id}>
|
||||
{data.brandDesc}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>성별</InputLabel>
|
||||
<SelectInput value={searchParams.gender} onChange={e => onSearch({ gender: e.target.value }, false)} >
|
||||
<option value='ALL'>전체</option>
|
||||
{opGender.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
];
|
||||
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
|
||||
};
|
||||
|
||||
export default ItemDictionarySearchBar;
|
||||
@@ -36,6 +36,7 @@ import UserCreateLogSearchBar, { useUserCreateLogSearch } from './UserCreateLogS
|
||||
import UserLoginLogSearchBar, { useUserLoginLogSearch } from './UserLoginLogSearchBar';
|
||||
import UserSnapshotLogSearchBar, { useUserSnapshotLogSearch } from './UserSnapshotLogSearchBar';
|
||||
import AssetsIndexSearchBar, { useAssetsIndexSearch } from './AssetsIndexSearchBar';
|
||||
import ItemDictionarySearchBar, { useItemDictionarySearch } from './ItemDictionarySearchBar';
|
||||
import LandAuctionSearchBar from './LandAuctionSearchBar';
|
||||
import CaliumRequestSearchBar from './CaliumRequestSearchBar';
|
||||
|
||||
@@ -93,6 +94,8 @@ export {
|
||||
UserSnapshotLogSearchBar,
|
||||
useUserSnapshotLogSearch,
|
||||
AssetsIndexSearchBar,
|
||||
useAssetsIndexSearch
|
||||
useAssetsIndexSearch,
|
||||
ItemDictionarySearchBar,
|
||||
useItemDictionarySearch,
|
||||
};
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@ const resources = {
|
||||
FILE_IMAGE_UPLOAD_ERROR: "이미지 업로드 중 오류가 발생했습니다.",
|
||||
FILE_NOT_EXIT_ERROR: "유효하지 않은 파일입니다.",
|
||||
FILE_SIZE_OVER_ERROR: "파일의 사이즈가 5MB를 초과하였습니다.",
|
||||
FILE_KOREAN_NAME_WARNING: "한글이름이 들어간 파일은 등록할 수 없습니다.",
|
||||
//파일명칭
|
||||
FILE_INDEX_USER_CONTENT: 'Caliverse_User_Index.xlsx',
|
||||
FILE_INDEX_USER_RETENTION: 'Caliverse_Retention.csv',
|
||||
@@ -171,6 +172,7 @@ const resources = {
|
||||
FILE_INDEX_ITEM_ACQUIRE: 'Caliverse_Item_Acquire.csv',
|
||||
FILE_INDEX_ITEM_CONSUME: 'Caliverse_Item_Consume.csv',
|
||||
FILE_INDEX_ASSETS_CURRENCY: 'Caliverse_Assets_Currency.csv',
|
||||
FILE_INDEX_STAT_CURRENCY: 'Caliverse_Stat_Currency.csv',
|
||||
FILE_INDEX_ASSETS_ITEM: 'Caliverse_Assets_Item.csv',
|
||||
FILE_CALIUM_REQUEST: 'Caliverse_Calium_Request.xlsx',
|
||||
FILE_LAND_AUCTION: 'Caliverse_Land_Auction.xlsx',
|
||||
@@ -183,6 +185,7 @@ const resources = {
|
||||
FILE_GAME_LOG_ITEM: 'Caliverse_Game_Log_Item',
|
||||
FILE_GAME_LOG_CURRENCY_ITEM: 'Caliverse_Game_Log_Currecy_Item',
|
||||
FILE_CURRENCY_INDEX: 'Caliverse_Currency_Index',
|
||||
FILE_DICTIONARY_ITEM: 'Caliverse_Dictionary_Item',
|
||||
//서버 에러메시지
|
||||
DYNAMODB_NOT_USER: '유저 정보를 확인해주세요.',
|
||||
NICKNAME_EXIT_ERROR: '해당 닉네임이 존재합니다.',
|
||||
|
||||
359
src/pages/DataManage/MetaItemView.js
Normal file
359
src/pages/DataManage/MetaItemView.js
Normal file
@@ -0,0 +1,359 @@
|
||||
import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { AnimatedPageWrapper, InfoCard } from '../../components/common/Layout';
|
||||
import {
|
||||
Title,
|
||||
TableStyle,
|
||||
FormWrapper,
|
||||
TableWrapper,
|
||||
TableActionButton,
|
||||
TableDetailRow,
|
||||
TableDetailContainer,
|
||||
TableDetailFlex,
|
||||
TableDetailColumn,
|
||||
DownloadContainer, CircularProgressWrapper,
|
||||
} from '../../styles/Components';
|
||||
|
||||
import { useDataFetch, withAuth } from '../../hooks/hook';
|
||||
import {
|
||||
authType,
|
||||
} from '../../assets/data';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
AnimatedTabs,
|
||||
TopButton,
|
||||
ViewTableInfo,
|
||||
} from '../../components/common';
|
||||
import { TableSkeleton } from '../../components/Skeleton/TableSkeleton';
|
||||
import CircularProgress from '../../components/common/CircularProgress';
|
||||
|
||||
import {
|
||||
INITIAL_PAGE_LIMIT, INITIAL_PAGE_SIZE,
|
||||
} from '../../assets/data/adminConstants';
|
||||
import ExcelExportButton from '../../components/common/button/ExcelExportButton';
|
||||
import Pagination from '../../components/common/Pagination/Pagination';
|
||||
import { useItemDictionarySearch, ItemDictionarySearchBar } from '../../components/searchBar';
|
||||
import { numberFormatter } from '../../utils';
|
||||
import { BrandView } from '../../apis';
|
||||
import { opGender, opItemLargeType, opItemSmallType } from '../../assets/data/options';
|
||||
import { languageNames } from '../../assets/data/types';
|
||||
|
||||
const BusinessLogView = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const { t } = useTranslation();
|
||||
const tableRef = useRef(null);
|
||||
|
||||
const [expandedRows, setExpandedRows] = useState({});
|
||||
const [activeLanguage, setActiveLanguage] = useState('ko');
|
||||
|
||||
const [downloadState, setDownloadState] = useState({
|
||||
loading: false,
|
||||
progress: 0
|
||||
});
|
||||
|
||||
const {
|
||||
searchParams,
|
||||
loading: dataLoading,
|
||||
data: dataList,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
handleOrderByChange,
|
||||
handlePageSizeChange,
|
||||
updateSearchParams
|
||||
} = useItemDictionarySearch(token, INITIAL_PAGE_SIZE);
|
||||
|
||||
const {
|
||||
data: brandData
|
||||
} = useDataFetch(() => BrandView(token), [token]);
|
||||
|
||||
useEffect(()=>{
|
||||
setDownloadState({
|
||||
loading: false,
|
||||
progress: 0
|
||||
});
|
||||
},[dataList]);
|
||||
|
||||
const toggleRowExpand = (index) => {
|
||||
setExpandedRows(prev => ({
|
||||
...prev,
|
||||
[index]: !prev[index]
|
||||
}));
|
||||
};
|
||||
|
||||
const tableHeaders = useMemo(() => {
|
||||
return [
|
||||
{ id: 'largeType', label: '대분류', width: '100px' },
|
||||
{ id: 'smallType', label: '소분류', width: '120px' },
|
||||
{ id: 'itemName', label: '아이템명', width: '150px' },
|
||||
{ id: 'itemId', label: '아이템 ID', width: '120px' },
|
||||
{ id: 'brand', label: '브랜드', width: '150px' },
|
||||
{ id: 'gender', label: '성별', width: '100px' },
|
||||
{ id: 'currencySellType', label: '판매시 획득 재화', width: '120px' },
|
||||
{ id: 'currencySellPrice', label: '판매시 획득 재화량', width: '120px' },
|
||||
{ id: 'currencyBuyType', label: '구매시 획득 재화', width: '120px' },
|
||||
{ id: 'currencyBuyPrice', label: '구매시 획득 재화량', width: '120px' },
|
||||
{ id: 'currencyBuyRate', label: '구매시 할인율', width: '120px' },
|
||||
{ id: 'details', label: '상세 정보', width: '100px' }
|
||||
];
|
||||
}, []);
|
||||
|
||||
const renderDetailInfo = useCallback((data, title, keyPrefix, index) => {
|
||||
if (!data) return null;
|
||||
|
||||
if (typeof data === 'object' && !Array.isArray(data)) {
|
||||
return (
|
||||
<InfoCard
|
||||
title={title}
|
||||
data={data}
|
||||
keyPrefix={`${keyPrefix}-${index}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const flattenedData = data.reduce((acc, info, dataIndex) => {
|
||||
Object.entries(info).forEach(([key, value]) => {
|
||||
acc[`${dataIndex + 1}_${key}`] = value;
|
||||
});
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return (
|
||||
<InfoCard
|
||||
title={title}
|
||||
data={flattenedData}
|
||||
keyPrefix={`${keyPrefix}-${index}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
// 언어별 테이블 렌더링
|
||||
const renderTableForLanguage = useCallback((languageKey) => {
|
||||
// 해당 언어의 아이템 리스트 가져오기
|
||||
const languageItemList = dataList?.item_list?.[languageKey] || [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||
<>
|
||||
<TableWrapper>
|
||||
<TableStyle ref={tableRef}>
|
||||
<thead>
|
||||
<tr>
|
||||
{tableHeaders.map(header => (
|
||||
<th key={header.id} width={header.width}>{header.label}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{languageItemList.length > 0 ? languageItemList.map((item, index) => (
|
||||
<Fragment key={`${languageKey}-${index}`}>
|
||||
<tr>
|
||||
<td>{opItemLargeType.find(data => data.value === item.type_large)?.name}</td>
|
||||
<td>{opItemSmallType.find(data => data.value === item.type_small)?.name}</td>
|
||||
<td>{item.item_name}</td>
|
||||
<td>{item.item_id}</td>
|
||||
<td>{item.brand === '' ? '-' : item.brand}</td>
|
||||
<td>{opGender.find(data => data.value === item.gender)?.name || '남녀공용'}</td>
|
||||
<td>{item.sell_type}</td>
|
||||
<td>{numberFormatter.formatCurrency(item.sell_price)}</td>
|
||||
<td>{item.buy_type}</td>
|
||||
<td>{numberFormatter.formatCurrency(item.buy_price)}</td>
|
||||
<td>{item.buy_discount_rate}</td>
|
||||
<td>
|
||||
<TableActionButton onClick={() => toggleRowExpand(`${languageKey}-${index}`)}>
|
||||
{expandedRows[`${languageKey}-${index}`] ? '접기' : '상세보기'}
|
||||
</TableActionButton>
|
||||
</td>
|
||||
</tr>
|
||||
{expandedRows[`${languageKey}-${index}`] && (
|
||||
<TableDetailRow>
|
||||
<td colSpan={tableHeaders.length}>
|
||||
<TableDetailContainer>
|
||||
<TableDetailFlex>
|
||||
<TableDetailColumn>
|
||||
{renderDetailInfo(item.country, "Count 정보", "country", `${languageKey}-${index}`)}
|
||||
{renderDetailInfo(item.expire, "아이템 만료 정보", "expire", `${languageKey}-${index}`)}
|
||||
{renderDetailInfo(item.trade, "Trade 정보", "trade", `${languageKey}-${index}`)}
|
||||
</TableDetailColumn>
|
||||
<TableDetailColumn>
|
||||
{renderDetailInfo(item.attrib, "속성 정보", "attrib", `${languageKey}-${index}`)}
|
||||
{renderDetailInfo(item.etc, "기타 정보", "etc", `${languageKey}-${index}`)}
|
||||
</TableDetailColumn>
|
||||
</TableDetailFlex>
|
||||
</TableDetailContainer>
|
||||
</td>
|
||||
</TableDetailRow>
|
||||
)}
|
||||
</Fragment>
|
||||
)) : (
|
||||
<tr>
|
||||
<td colSpan={tableHeaders.length} style={{ textAlign: 'center', padding: '20px' }}>
|
||||
해당 언어의 데이터가 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</TableWrapper>
|
||||
{languageItemList.length > 0 &&
|
||||
<Pagination
|
||||
postsPerPage={searchParams.page_size}
|
||||
totalPosts={dataList?.total_all}
|
||||
setCurrentPage={handlePageChange}
|
||||
currentPage={searchParams.page_no}
|
||||
pageLimit={INITIAL_PAGE_LIMIT}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}, [dataList, dataLoading, expandedRows, tableHeaders, searchParams, handlePageChange]);
|
||||
|
||||
// 언어별 탭 아이템 생성
|
||||
const tabItems = useMemo(() => {
|
||||
// 실제 데이터에서 사용 가능한 언어만 탭으로 생성
|
||||
const availableLanguages = dataList?.item_list ? Object.keys(dataList.item_list) : ['ko', 'en', 'ja'];
|
||||
|
||||
return availableLanguages.map(langKey => ({
|
||||
key: langKey,
|
||||
label: languageNames[langKey.charAt(0).toUpperCase() + langKey.slice(1)] || langKey.toUpperCase(),
|
||||
children: renderTableForLanguage(langKey)
|
||||
}));
|
||||
}, [dataList, renderTableForLanguage]);
|
||||
|
||||
|
||||
const handleTabChange = (key) => {
|
||||
setActiveLanguage(key);
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatedPageWrapper>
|
||||
<Title>아이템 백과사전 조회</Title>
|
||||
<FormWrapper>
|
||||
<ItemDictionarySearchBar
|
||||
searchParams={searchParams}
|
||||
onSearch={(newParams, executeSearch = true) => {
|
||||
if (executeSearch) {
|
||||
handleSearch(newParams);
|
||||
} else {
|
||||
updateSearchParams(newParams);
|
||||
}
|
||||
}}
|
||||
onReset={handleReset}
|
||||
brandData={brandData}
|
||||
/>
|
||||
</FormWrapper>
|
||||
<ViewTableInfo orderType="asc" total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
|
||||
<DownloadContainer>
|
||||
<ExcelExportButton
|
||||
functionName="BusinessLogExport"
|
||||
params={() => {
|
||||
const params = searchParams;
|
||||
params.lang = activeLanguage;
|
||||
return params;
|
||||
}}
|
||||
fileName={t('FILE_DICTIONARY_ITEM')}
|
||||
onLoadingChange={setDownloadState}
|
||||
dataSize={dataList?.total_all}
|
||||
/>
|
||||
{downloadState.loading && (
|
||||
<CircularProgressWrapper>
|
||||
<CircularProgress
|
||||
progress={downloadState.progress}
|
||||
size={36}
|
||||
progressColor="#4A90E2"
|
||||
/>
|
||||
</CircularProgressWrapper>
|
||||
)}
|
||||
</DownloadContainer>
|
||||
</ViewTableInfo>
|
||||
{
|
||||
dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||
<AnimatedTabs
|
||||
items={tabItems}
|
||||
activeKey={activeLanguage}
|
||||
onChange={handleTabChange}
|
||||
tabPosition="left"
|
||||
/>
|
||||
}
|
||||
|
||||
|
||||
{/*{dataLoading ? <TableSkeleton width='100%' count={15} /> :*/}
|
||||
{/* <>*/}
|
||||
{/* <TableWrapper>*/}
|
||||
{/* <TableStyle ref={tableRef}>*/}
|
||||
{/* <thead>*/}
|
||||
{/* <tr>*/}
|
||||
{/* {tableHeaders.map(header => (*/}
|
||||
{/* <th key={header.id} width={header.width}>{header.label}</th>*/}
|
||||
{/* ))}*/}
|
||||
{/* </tr>*/}
|
||||
{/* </thead>*/}
|
||||
{/* <tbody>*/}
|
||||
{/* {dataList?.item_list?.map((item, index) => (*/}
|
||||
{/* <Fragment key={index}>*/}
|
||||
{/* <tr>*/}
|
||||
{/* <td>{opItemLargeType.find(data => data.value === item.type_large)?.name}</td>*/}
|
||||
{/* <td>{opItemSmallType.find(data => data.value === item.type_small)?.name}</td>*/}
|
||||
{/* <td>{item.item_name}</td>*/}
|
||||
{/* <td>{item.item_id}</td>*/}
|
||||
{/* <td>{item.brand === '' ? '-' : item.brand}</td>*/}
|
||||
{/* <td>{opGender.find(data => data.value === item.gender)?.name || '남녀공용'}</td>*/}
|
||||
{/* <td>{item.sell_type}</td>*/}
|
||||
{/* <td>{numberFormatter.formatCurrency(item.sell_price)}</td>*/}
|
||||
{/* <td>{item.buy_type}</td>*/}
|
||||
{/* <td>{numberFormatter.formatCurrency(item.buy_price)}</td>*/}
|
||||
{/* <td>{item.buy_discount_rate}</td>*/}
|
||||
{/* <td>*/}
|
||||
{/* <TableActionButton onClick={() => toggleRowExpand(index)}>*/}
|
||||
{/* {expandedRows[index] ? '접기' : '상세보기'}*/}
|
||||
{/* </TableActionButton>*/}
|
||||
{/* </td>*/}
|
||||
{/* </tr>*/}
|
||||
{/* {expandedRows[index] && (*/}
|
||||
{/* <TableDetailRow>*/}
|
||||
{/* <td colSpan={tableHeaders.length}>*/}
|
||||
{/* <TableDetailContainer>*/}
|
||||
{/* <TableDetailFlex>*/}
|
||||
{/* <TableDetailColumn>*/}
|
||||
{/* {renderDetailInfo(item.country, "Count 정보", "country", index)}*/}
|
||||
{/* {renderDetailInfo(item.expire, "아이템 만료 정보", "expire", index)}*/}
|
||||
{/* {renderDetailInfo(item.trade, "Trade 정보", "trade", index)}*/}
|
||||
{/* </TableDetailColumn>*/}
|
||||
{/* <TableDetailColumn>*/}
|
||||
{/* {renderDetailInfo(item.attrib, "속성 정보", "attrib", index)}*/}
|
||||
{/* {renderDetailInfo(item.etc, "기타 정보", "etc", index)}*/}
|
||||
{/* </TableDetailColumn>*/}
|
||||
{/* </TableDetailFlex>*/}
|
||||
{/* </TableDetailContainer>*/}
|
||||
{/* </td>*/}
|
||||
{/* </TableDetailRow>*/}
|
||||
{/* )}*/}
|
||||
{/* </Fragment>*/}
|
||||
{/* ))}*/}
|
||||
{/* </tbody>*/}
|
||||
{/* </TableStyle>*/}
|
||||
{/* </TableWrapper>*/}
|
||||
{/* {dataList?.item_list &&*/}
|
||||
{/* <Pagination*/}
|
||||
{/* postsPerPage={searchParams.page_size}*/}
|
||||
{/* totalPosts={dataList?.total_all}*/}
|
||||
{/* setCurrentPage={handlePageChange}*/}
|
||||
{/* currentPage={searchParams.page_no}*/}
|
||||
{/* pageLimit={INITIAL_PAGE_LIMIT}*/}
|
||||
{/* />*/}
|
||||
{/* }*/}
|
||||
{/* <TopButton />*/}
|
||||
{/* </>*/}
|
||||
{/*}*/}
|
||||
</AnimatedPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default withAuth(authType.businessLogRead)(BusinessLogView);
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as optionsConfig from '../assets/data/options';
|
||||
import { logFieldLabels } from '../assets/data/data';
|
||||
import { FieldLabels } from '../assets/data/data';
|
||||
|
||||
export const convertKTC = (dt, nation = true) => {
|
||||
if (!dt) return "";
|
||||
@@ -77,12 +77,12 @@ export const loadConfig = async (configPath) => {
|
||||
};
|
||||
|
||||
export const getFieldLabel = (key, value) => {
|
||||
if (logFieldLabels[key]) {
|
||||
return logFieldLabels[key];
|
||||
if (FieldLabels[key]) {
|
||||
return FieldLabels[key];
|
||||
}
|
||||
|
||||
if (typeof value === 'string' && logFieldLabels[value]) {
|
||||
return logFieldLabels[value];
|
||||
if (typeof value === 'string' && FieldLabels[value]) {
|
||||
return FieldLabels[value];
|
||||
}
|
||||
|
||||
return key;
|
||||
|
||||
Reference in New Issue
Block a user