Compare commits

...

2 Commits

Author SHA1 Message Date
9d06246aba 칼리움 요청 실패 표시 및 클릭시 페이지 이동 처리 2025-05-23 15:58:04 +09:00
1532793cc1 LogView 리팩토링
LogDetailModal 생성
화면별 히스토리(LogDetailModal) 적용
2025-05-22 14:55:37 +09:00
37 changed files with 2085 additions and 362 deletions

View File

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

View File

@@ -8,5 +8,8 @@ export const ONE_MINUTE_MS = 60000;
export const ONE_MINUTE_SECOND = 60;
export const AUCTION_MIN_MINUTE_TIME = 15; // 15분
export const IMAGE_MAX_SIZE = 5242880;
export const STORAGE_MAIL_COPY = 'copyMailData';
export const STORAGE_BUSINESS_LOG_SEARCH = 'businessLogSearchParam';
export const LOG_ACTION_FAIL_CALIUM_ECHO = 'FailCaliumEchoSystem';
export { INITIAL_PAGE_SIZE, INITIAL_CURRENT_PAGE, INITIAL_PAGE_LIMIT };

View File

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

View File

@@ -1,7 +1,9 @@
import itemAPI from './itemAPI.json';
import menuBannerAPI from './menuBannerAPI.json';
import historyAPI from './historyAPI.json';
export {
itemAPI,
menuBannerAPI
menuBannerAPI,
historyAPI
};

View File

@@ -5,6 +5,14 @@ export const benItems = [
"19010005"
];
export const historyBenField = [
"create_by",
"create_dt",
"update_by",
"update_dt",
"id"
]
export const HourList = ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'];
export const MinuteList = [
@@ -113,4 +121,75 @@ export const STATUS_STYLES = {
background: '#4287f5',
color: 'white'
},
};
};
export const logFieldLabels = {
// DynamoDB 필드
'attribFieldName': '속성 명',
'pk': '파티션 키',
'sk': '정렬 키',
'DocType': '문서 타입',
'CreatedDateTime': '생성 일시',
'UpdatedDateTime': '수정 일시',
'DeletedDateTime': '삭제 일시',
'RestoredDateTime': '복원 일시',
'INSERT': '등록',
'UPDATE': '수정',
'DELETE': '삭제',
//기본
'id': 'ID',
'userId': '사용자 ID',
'userIP': '사용자 IP',
'timestamp': '타임스탬프',
'message': '메시지',
'tranId': '트랜잭션 ID',
'group_id': '그룹 ID',
'update_dt': '수정 일시',
'updateDt': '수정 일시',
'update_by': '수정자',
'create_dt': '생성 일시',
'createDt': '생성 일시',
'create_by': '생성자',
'status': '상태',
'deleted': '삭제 여부',
// 이벤트 필드 관련
'eventId': '이벤트 ID',
'eventName': '이벤트 명',
'repeatType': '반복 타입',
'eventOperationTime': '운영 시간(초)',
'eventStartDt': '시작 시간',
'eventEndDt': '종료 시간',
'roundTime': '라운드 시간(초)',
'roundCount': '라운드 수',
'hotTime': '핫타임',
'configId': '설정 ID',
'rewardGroupId': '보상 그룹 ID',
'attrib_type': '속성 타입',
'event_id': '이벤트 ID',
'is_active': '활성화 여부',
'start_day': '시작일',
'start_hour': '시작 시간',
'start_min': '시작 분',
'end_date': '종료 일시',
'instance_id': '인스턴스 ID',
'once_period_type': '주기 타입',
'day_of_week_type': '요일 타입',
'ffa_config_data_id': 'FFA 설정 ID',
'ffa_reward_group_id': 'FFA 보상 그룹 ID',
'ffa_hot_time': 'FFA 핫타임',
'round_count': '라운드 수',
};
export const historyTables = {
userBlock: 'black_list',
landAuction: 'land_auction',
landOwnerChange: 'land_ownership_changes',
event: 'event',
mail: 'mail',
notice: 'notice',
battleEvent: 'battle_event',
caliumRequest: 'calium_request',
}

View File

@@ -100,7 +100,7 @@ export const menuConfig = {
permissions: {
read: authType.gameLogRead
},
view: true,
view: false,
authLevel: adminAuthLevel.NONE
},
cryptview: {
@@ -108,7 +108,7 @@ export const menuConfig = {
permissions: {
read: authType.cryptoRead
},
view: true,
view: false,
authLevel: adminAuthLevel.NONE
},
businesslogview: {

View File

@@ -167,6 +167,11 @@ export const userType2 = [
{ value: 'NICKNAME', name: '닉네임' },
];
export const adminUserType = [
{ value: 'ID', name: 'ID(이메일)' },
{ value: 'NAME', name: '이름' },
];
export const userSearchType2 = [
{ value: 'GUID', name: 'GUID' },
{ value: 'NICKNAME', name: '닉네임' },
@@ -336,6 +341,75 @@ export const opItemType = [
{ value: 'BEAUTY', name: '뷰티' },
]
export const opHistoryType = [
{ value: '', name: '전체' },
{ value: 'LOGIN_PERMITTED', name: '로그인 승인' },
{ value: 'ADMIN_INFO_UPDATE', name: '운영자 정보 수정' },
{ value: 'ADMIN_INFO_DELETE', name: '운영자 정보 삭제' },
{ value: 'PASSWORD_INIT', name: '비밀번호 초기화' },
{ value: 'USER_INFO_UPDATE', name: '유저 정보 변경' },
{ value: 'GROUP_AUTH_UPDATE', name: '그룹 권한 수정' },
{ value: 'GROUP_DELETE', name: '그룹 삭제' },
{ value: 'NOTICE_DELETE', name: '공지사항 삭제' },
{ value: 'NOTICE_ADD', name: '공지사항 등록' },
{ value: 'NOTICE_UPDATE', name: '공지사항 수정' },
{ value: 'NOTICE_SEND_FAIL', name: '공지사항 전송 실패' },
{ value: 'MAIL_DELETE', name: '우편 삭제' },
{ value: 'MAIL_ADD', name: '우편 등록' },
{ value: 'MAIL_UPDATE', name: '우편 수정' },
{ value: 'MAIL_SEND', name: '우편 전송' },
{ value: 'MAIL_SEND_FAIL', name: '우편 전송 실패' },
{ value: 'MAIL_ITEM_DELETE', name: '우편 아이템 삭제' },
{ value: 'MAIL_ITEM_UPDATE', name: '우편 아이템 수정' },
{ value: 'BLACKLIST_ADD', name: '유저 제재 등록' },
{ value: 'BLACKLIST_UPDATE', name: '유저 제재 수정' },
{ value: 'BLACKLIST_DELETE', name: '유저 제재 삭제' },
{ value: 'REPORT_DELETE', name: '신고내역 삭제' },
{ value: 'USER_ITEM_DELETE', name: '유저 아이템 삭제' },
{ value: 'SCHEDULE_MAIL_FAIL', name: '메일 스케줄 실패' },
{ value: 'SCHEDULE_NOTICE_FAIL', name: '공지 스케줄 실패' },
{ value: 'SCHEDULE_EVENT_FAIL', name: '이벤트 스케줄 실패' },
{ value: 'USER_MAIL_DELETE', name: '유저 메일 삭제' },
{ value: 'INVENTORY_ITEM_DELETE', name: '인벤토리 아이템 삭제' },
{ value: 'INVENTORY_ITEM_UPDATE', name: '인벤토리 아이템 수정' },
{ value: 'EVENT_ADD', name: '이벤트 등록' },
{ value: 'EVENT_UPDATE', name: '이벤트 수정' },
{ value: 'EVENT_DELETE', name: '이벤트 삭제' },
{ value: 'CALIUM_ADD', name: '칼리움 요청 등록' },
{ value: 'CALIUM_SAVE', name: '칼리움 저장' },
{ value: 'CALIUM_TRANSFER', name: '칼리움 전송' },
{ value: 'CALIUM_TOTAL_UPDATE', name: '칼리움 충전' },
{ value: 'LAND_AUCTION_ADD', name: '랜드경매 등록' },
{ value: 'LAND_AUCTION_UPDATE', name: '랜드경매 수정' },
{ value: 'LAND_AUCTION_DELETE', name: '랜드경매 삭제' },
{ value: 'BATTLE_EVENT_ADD', name: '전투시스템 이벤트 등록' },
{ value: 'BATTLE_EVENT_UPDATE', name: '전투시스템 이벤트 수정' },
{ value: 'BATTLE_EVENT_DELETE', name: '전투시스템 이벤트 삭제' },
{ value: 'LAND_OWNER_CHANGE_ADD', name: '랜드 소유권 변경 등록' },
{ value: 'LAND_OWNER_CHANGE_UPDATE', name: '랜드 소유권 변경 수정' },
{ value: 'LAND_OWNER_CHANGE_DELETE', name: '랜드 소유권 변경 예약 취소' },
{ value: 'LAND_OWNER_CHANGE_MAIL', name: '랜드 소유권 변경 우편' },
{ value: 'LAND_OWNED_INITIALIZE', name: '랜드 소유권 정보 초기화' },
{ value: 'LAND_DESC_INITIALIZE', name: '랜드 정보 초기화' },
{ value: 'LAND_AUCTION_INITIALIZE', name: '랜드 경매 초기화' },
{ value: 'MENU_BANNER_ADD', name: '메뉴 배너 등록' },
{ value: 'MENU_BANNER_UPDATE', name: '메뉴 배너 수정' },
{ value: 'MENU_BANNER_DELETE', name: '메뉴 배너 삭제' },
{ value: 'ITEM_UPDATE', name: '아이템 수정' },
{ value: 'ITEM_DELETE', name: '아이템 삭제' },
{ value: 'USER_ADMIN_AUTH_UPDATE', name: '유저 관리자 권한 수정' },
{ value: 'NICKNAME_REGISTRY_DELETE', name: '닉네임 레지스트리 삭제' },
{ value: 'NICKNAME_REGISTRY_ADD', name: '닉네임 레지스트리 등록' },
{ value: 'NICKNAME_UPDATE', name: '닉네임 수정' },
{ value: 'DATA_INIT_ADD', name: '데이터 초기화 등록' },
];
export const opDBType = [
{ value: '', name: '전체'},
{ value: 'dynamoDB', name: 'dynamoDB'},
{ value: 'MySql', name: 'MySql'},
]
// export const logAction = [
// { value: "None", name: "ALL" },
// { value: "AIChatDeleteCharacter", name: "NPC 삭제" },
@@ -658,6 +732,14 @@ export const logAction = [
{ value: "BeaconCreate", name: "BeaconCreate" },
{ value: "BeaconEdit", name: "BeaconEdit" },
{ value: "BeaconSell", name: "BeaconSell" },
{ value: "BeaconShopRegisterItem", name: "BeaconShopRegisterItem" },
{ value: "BeaconShopReturnItem", name: "BeaconShopReturnItem" },
{ value: "BeaconShopPurchaseItem", name: "BeaconShopPurchaseItem" },
{ value: "BeaconShopReceivePaymentForSales", name: "BeaconShopReceivePaymentForSales" },
{ value: "BeaconShopSearchItem", name: "BeaconShopSearchItem" },
{ value: "BeaconShopUpdateDailyCount", name: "BeaconShopUpdateDailyCount" },
{ value: "BeaconShopDeleteRecord", name: "BeaconShopDeleteRecord" },
{ value: "BeaconShopDeactiveItems", name: "BeaconShopDeactiveItems" },
{ value: "BrokerApiAdmin", name: "BrokerApiAdmin" },
{ value: "BrokerApiPlanetAuth", name: "BrokerApiPlanetAuth" },
{ value: "BrokerApiUserExchangeOrderCompleted", name: "BrokerApiUserExchangeOrderCompleted" },
@@ -708,6 +790,8 @@ export const logAction = [
{ value: "CheatCommandSendMail", name: "CheatCommandSendMail" },
{ value: "CheatCommandShopProductInit", name: "CheatCommandShopProductInit" },
{ value: "CheatCommandShopProductRenewal", name: "CheatCommandShopProductRenewal" },
{ value: "CheatCommandBeaconShopItemTimeChange", name: "CheatCommandBeaconShopItemTimeChange" },
{ value: "CheatCommandDailyLimitInit", name: "CheatCommandDailyLimitInit" },
{ value: "ClaimReward", name: "ClaimReward" },
{ value: "ConvertCalium", name: "ConvertCalium" },
{ value: "ConvertExchangeCalium", name: "ConvertExchangeCalium" },
@@ -744,6 +828,8 @@ export const logAction = [
{ value: "ItemTattooChangeAttribute", name: "ItemTattooChangeAttribute" },
{ value: "ItemTattooLevelUp", name: "ItemTattooLevelUp" },
{ value: "ItemUse", name: "ItemUse" },
{ value: "ItemDestroyByUser", name: "ItemDestroyByUser" },
{ value: "ItemDestoryByExpiration", name: "ItemDestoryByExpiration" },
{ value: "JoinInstance", name: "JoinInstance" },
{ value: "JoinParty", name: "JoinParty" },
{ value: "JoinPartyInstance", name: "JoinPartyInstance" },
@@ -770,7 +856,7 @@ export const logAction = [
{ value: "ProductGive", name: "ProductGive" },
{ value: "ProductOpenFailed", name: "ProductOpenFailed" },
{ value: "ProductOpenSuccess", name: "ProductOpenSuccess" },
{ value: "QuestMailSend", name: "QuestMailSend" },
{ value: "QuestMainAssign", name: "QuestMainAssign" },
{ value: "QuestMainAbort", name: "QuestMainAbort" },
{ value: "QuestMainAssignByDialogue", name: "QuestMainAssignByDialogue" },
{ value: "QuestMainAssignForce", name: "QuestMainAssignForce" },
@@ -778,6 +864,7 @@ export const logAction = [
{ value: "QuestMainRepeatTimeInit", name: "QuestMainRepeatTimeInit" },
{ value: "QuestMainRepeatTimeRefresh", name: "QuestMainRepeatTimeRefresh" },
{ value: "QuestMainReward", name: "QuestMainReward" },
{ value: "QuestMailSend", name: "QuestMailSend" },
{ value: "QuestMainTask", name: "QuestMainTask" },
{ value: "QuestTaskUpdate", name: "QuestTaskUpdate" },
{ value: "RefuseFriendRequest", name: "RefuseFriendRequest" },
@@ -865,6 +952,9 @@ export const logDomain = [
{ value: "BattleSnapshot", name: "BattleSnapshot" },
{ value: "BeaconCreate", name: "BeaconCreate" },
{ value: "Beacon", name: "Beacon" },
{ value: "BeaconShop", name: "BeaconShop" },
{ value: "BeaconShopSoldRecord", name: "BeaconShopSoldRecord" },
{ value: "BeaconShopSoldPrice", name: "BeaconShopSoldPrice" },
{ value: "BrokerApi", name: "BrokerApi" },
{ value: "Buff", name: "Buff" },
{ value: "Building", name: "Building" },
@@ -888,6 +978,7 @@ export const logDomain = [
{ value: "FarmingReward", name: "FarmingReward" },
{ value: "GameLogInOut", name: "GameLogInOut" },
{ value: "Item", name: "Item" },
{ value: "IgmApi", name: "IgmApi" },
{ value: "MyHome", name: "MyHome" },
{ value: "Land", name: "Land" },
{ value: "LandAuction", name: "LandAuction" },
@@ -905,6 +996,9 @@ export const logDomain = [
{ value: "PackageLastOrderRecode", name: "PackageLastOrderRecode" },
{ value: "PackageRepeat", name: "PackageRepeat" },
{ value: "PackageState", name: "PackageState" },
{ value: "PlanetProviderAuth", name: "PlanetProviderAuth" },
{ value: "PlanetUserAuth", name: "PlanetUserAuth" },
{ value: "PlanetItemExchange", name: "PlanetItemExchange" },
{ value: "QuestMain", name: "QuestMain" },
{ value: "QuestUgq", name: "QuestUgq" },
{ value: "QuestMail", name: "QuestMail" },

View File

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

View File

@@ -0,0 +1,56 @@
{
"id": "historyTable",
"selection": {
"type": "single",
"idField": "id"
},
"header": {
"countType": "total",
"orderType": "desc",
"pageType": "default",
"buttons": []
},
"columns": [
{
"id": "timestamp",
"type": "date",
"width": "200px",
"title": "일시(KST)",
"format": {
"type": "function",
"name": "convertKTC"
}
},
{
"id": "dbType",
"type": "option",
"width": "100px",
"title": "DB타입",
"option_name": "opDBType"
},
{
"id": "historyType",
"type": "option",
"width": "150px",
"title": "이력종류",
"option_name": "opHistoryType"
},
{
"id": "userId",
"type": "text",
"width": "100px",
"title": "작업자"
},
{
"id": "detail",
"type": "button",
"width": "120px",
"title": "상세보기",
"text": "상세보기"
}
],
"sort": {
"defaultColumn": "timestamp",
"defaultDirection": "desc"
}
}

View File

@@ -73,12 +73,6 @@
"title": "제재 사유",
"option_name": "blockSanctions"
},
{
"id": "create_by",
"type": "text",
"width": "150px",
"title": "등록자"
},
{
"id": "detail",
"type": "button",
@@ -92,6 +86,13 @@
"id": "id"
}
}
},
{
"id": "history",
"type": "button",
"width": "120px",
"title": "히스토리",
"text": "히스토리"
}
],
"sort": {

View File

@@ -268,8 +268,6 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
}
}
console.log(configData)
return (
<>
<Modal min="760px" $view={detailView}>

View File

@@ -31,6 +31,7 @@ import { useAlert } from '../../../context/AlertProvider';
import { useLoading } from '../../../context/LoadingProvider';
import { alertTypes, currencyCodeTypes } from '../../../assets/data/types';
import { userType2 } from '../../../assets/data/options';
import { STORAGE_MAIL_COPY } from '../../../assets/data/adminConstants';
const MailDetailModal = ({ detailView, handleDetailView, content }) => {
const userInfo = useRecoilValue(authList);
@@ -151,14 +152,14 @@ const MailDetailModal = ({ detailView, handleDetailView, content }) => {
if (data.result === 'ERROR') {
showToast(data.data.message, { type: alertTypes.warning });
}else{
const itemIndex = resultData.item_list.findIndex(
data => data.item === item
);
if (itemIndex !== -1) {
showToast('MAIL_ITEM_ADD_DUPL', { type: alertTypes.warning });
return;
}
// const itemIndex = resultData.item_list.findIndex(
// data => data.item === item
// );
//
// if (itemIndex !== -1) {
// showToast('MAIL_ITEM_ADD_DUPL', { type: alertTypes.warning });
// return;
// }
const newItem = { item: item, item_cnt: itemCount, item_name: data.data.item_info.item_name };
resultData.item_list.push(newItem);
@@ -285,7 +286,7 @@ const MailDetailModal = ({ detailView, handleDetailView, content }) => {
};
// 복사한 데이터를 세션 스토리지에 저장
sessionStorage.setItem('copyMailData', JSON.stringify(mailData));
sessionStorage.setItem(STORAGE_MAIL_COPY, JSON.stringify(mailData));
navigate('/servicemanage/mail/mailregist');
};

View File

@@ -1,13 +1,14 @@
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
import { useCallback, useEffect, useState } from 'react';
import { logAction, logDomain, userSearchType2 } from '../../../assets/data/options';
import { BusinessLogList } from '../../../apis/Log';
import { useTranslation } from 'react-i18next';
import {SearchFilter} from '../';
import { useAlert } from '../../../context/AlertProvider';
import { alertTypes } from '../../../assets/data/types';
export const useBusinessLogSearch = (token, initialPageSize, setAlertMsg) => {
const { t } = useTranslation();
export const useBusinessLogSearch = (token, initialPageSize) => {
const {showToast} = useAlert();
const [searchParams, setSearchParams] = useState({
search_type: 'GUID',
@@ -53,14 +54,16 @@ export const useBusinessLogSearch = (token, initialPageSize, setAlertMsg) => {
params
);
if(result.result === "ERROR_LOG_MEMORY_LIMIT"){
setAlertMsg(t('LOG_MEMORY_LIMIT_WARNING'))
showToast('LOG_MEMORY_LIMIT_WARNING', {type: alertTypes.error});
}else if(result.result === "ERROR_MONGODB_QUERY"){
setAlertMsg(t('LOG_MONGGDB_QUERY_WARNING'))
showToast('LOG_MONGGDB_QUERY_WARNING', {type: alertTypes.error});
}else if(result.result === "ERROR"){
showToast(result.result, {type: alertTypes.error});
}
setData(result.data);
return result.data;
} catch (error) {
console.error('Error fetching auction data:', error);
showToast('error', {type: alertTypes.error});
throw error;
} finally {
setLoading(false);

View File

@@ -1,12 +1,12 @@
import { BtnWrapper, InputLabel, TextInput } from '../../../styles/Components';
import Button from '../../common/button/Button';
import { InputLabel, TextInput } from '../../../styles/Components';
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { InitHistoryList } from '../../../apis/Data';
import { useAlert } from '../../../context/AlertProvider';
import { alertTypes } from '../../../assets/data/types';
export const useDataInitSearch = (token, setAlertMsg) => {
const { t } = useTranslation();
export const useDataInitSearch = (token) => {
const {showToast} = useAlert();
const [searchParams, setSearchParams] = useState({
tran_id: '',
@@ -39,9 +39,9 @@ export const useDataInitSearch = (token, setAlertMsg) => {
params
);
if(result.result === "ERROR_LOG_MEMORY_LIMIT"){
setAlertMsg(t('LOG_MEMORY_LIMIT_WARNING'))
showToast('LOG_MEMORY_LIMIT_WARNING',{type: alertTypes.error});
}else if(result.result === "ERROR_MONGODB_QUERY"){
setAlertMsg(t('LOG_MONGGDB_QUERY_WARNING'))
showToast('LOG_MONGGDB_QUERY_WARNING',{type: alertTypes.error});
}
setData(result.data);
return result.data;

View File

@@ -4,6 +4,7 @@ import { useState } from 'react';
import { TextInput, InputLabel, SelectInput, BtnWrapper } from '../../../styles/Components';
import Button from '../../common/button/Button';
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
import { opHistoryType } from '../../../assets/data/options';
const LogViewSearchBar = ({ handleSearch, resultData }) => {
const [searchData, setSearchData] = useState({
@@ -19,21 +20,7 @@ const LogViewSearchBar = ({ handleSearch, resultData }) => {
{ value: 'id', name: 'ID(이메일)' },
];
const logOption = [
{ value: '', name: '이력 선택' },
{ value: 'LOGIN_PERMITTED', name: '로그인 승인' },
{ value: 'ADMIN_INFO_UPDATE', name: '운영자 정보 수정' },
{ value: 'ADMIN_INFO_DELETE', name: '운영자 정보 삭제' },
{ value: 'PASSWORD_INIT', name: '비밀번호 초기화' },
{ value: 'USER_INFO_UPDATE', name: '유저 정보 변경' },
{ value: 'GROUP_AUTH_UPDATE', name: '그룹 권한 수정' },
{ value: 'GROUP_DELETE', name: '그룹 삭제' },
{ value: 'NOTICE_DELETE', name: '인게임 메시지 삭제' },
{ value: 'MAIL_DELETE', name: '우편 삭제' },
{ value: 'WHITELIST_DELETE', name: '화이트리스트 삭제' },
{ value: 'BLACKLIST_DELETE', name: '유저 제재 삭제' },
{ value: 'REPORT_DELETE', name: '신고내역 삭제' },
];
const handleReset = () => {
setSearchData({
@@ -85,7 +72,7 @@ const LogViewSearchBar = ({ handleSearch, resultData }) => {
<InputLabel>사용 이력</InputLabel>
<InputGroup>
<SelectInput value={searchData.logOption} onChange={e => setSearchData({ ...searchData, logOption: e.target.value })}>
{logOption.map((data, index) => (
{opHistoryType.map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>

View File

@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import PaginationIcon from '../../../assets/img/icon/icon-pagination.png';
import { INITIAL_CURRENT_PAGE } from '../../../assets/data/adminConstants';
const FrontPagination = ({
data, // 전체 데이터 배열
@@ -13,6 +14,10 @@ const FrontPagination = ({
}) => {
const [blockNum, setBlockNum] = useState(0);
useEffect(() => {
setCurrentPage(INITIAL_CURRENT_PAGE);
},[data])
// 전체 페이지 수 계산
const totalItems = data?.length || 0;
const maxPage = Math.ceil(totalItems / itemsPerPage);

View File

@@ -6,7 +6,7 @@ import {
TableInfo,
} from '../../../styles/Components';
import { ORDER_OPTIONS, PAGE_SIZE_OPTIONS, ViewTitleCountType } from '../../../assets/data';
import { TitleItem, TitleItemLabel, TitleItemValue } from '../../../styles/ModuleComponents';
import { TitleItem, TitleItemLabel, TitleItemLink, TitleItemValue } from '../../../styles/ModuleComponents';
import { DynamoPagination } from '../index';
import React from 'react';
@@ -14,6 +14,8 @@ const ViewTableInfo = ({
children,
total,
total_all,
fail_count,
onFailCountClick,
orderType = 'desc',
handleOrderBy,
pageType = 'default',
@@ -28,7 +30,7 @@ const ViewTableInfo = ({
{total !== undefined && total_all !== undefined &&
<ListCount>
{COUNT_TYPE_RENDERERS[countType] ?
COUNT_TYPE_RENDERERS[countType](total, total_all) :
COUNT_TYPE_RENDERERS[countType](total, total_all, fail_count, onFailCountClick) :
COUNT_TYPE_RENDERERS[ViewTitleCountType.total](total, total_all)}
</ListCount>
}
@@ -52,7 +54,7 @@ const ViewTableInfo = ({
const COUNT_TYPE_RENDERERS = {
[ViewTitleCountType.total]: (total, total_all) => `총 : ${total ?? 0} 건 / ${total_all ?? 0}`,
[ViewTitleCountType.calium]: (total, total_all) => (
[ViewTitleCountType.calium]: (total, total_all, fail_count, onFailCountClick) => (
<>
<TitleItem>
<TitleItemLabel>누적 충전</TitleItemLabel>
@@ -62,6 +64,10 @@ const COUNT_TYPE_RENDERERS = {
<TitleItemLabel>잔여 수량</TitleItemLabel>
<TitleItemValue color='#B39063' fontWeight='bold'>{total ?? 0}</TitleItemValue>
</TitleItem>
<TitleItem>
<TitleItemLabel>에코시스템 실패</TitleItemLabel>
<TitleItemLink color='#D33B27' fontWeight='bold' onClick={onFailCountClick}>{fail_count ?? 0}</TitleItemLink>
</TitleItem>
</>
),
};

View File

@@ -0,0 +1,253 @@
import { styled } from 'styled-components';
import React, { Fragment, useEffect, useState } from 'react';
import { Title } from '../../../styles/Components';
import { BtnWrapper, TableStyle } from '../../../styles/Components';
import Button from '../../../components/common/button/Button';
import Modal from '../../../components/common/modal/Modal';
import { TableWrapper } from '../../../styles/Components';
import { Tab, TabContent } from '../../../styles/ModuleComponents';
import { convertKTC, getFieldLabel } from '../../../utils';
import { historyBenField } from '../../../assets/data/data';
const LogDetailModal = ({ detailView,
handleDetailView,
detailData,
changedData,
viewMode = 'both',
title = '로그 상세정보'
}) => {
const [activeTab, setActiveTab] = useState('data');
// viewMode가 변경될 때 적절한 탭 활성화
useEffect(() => {
if (viewMode === 'data' || viewMode === 'changed') {
setActiveTab(viewMode);
}
}, [viewMode]);
// data 객체의 키-값 쌍을 테이블 형식으로 변환
const renderDataTable = () => {
if (!detailData || !detailData) return null;
// data 객체를 평탄화하는 함수 (중첩된 객체도 처리)
const flattenObject = (obj, prefix = '') => {
return Object.keys(obj).reduce((acc, key) => {
const newKey = prefix ? `${prefix}.${key}` : key;
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
// $date나 $oid 같은 특수 키를 포함하는 MongoDB 형식 객체 처리
if (obj[key].$date) {
acc[newKey] = new Date(obj[key].$date).toLocaleString('ko-KR');
return acc;
}
if (obj[key].$oid) {
acc[newKey] = obj[key].$oid;
return acc;
}
if (obj[key].$numberLong) {
acc[newKey] = obj[key].$numberLong;
return acc;
}
// 일반 중첩 객체는 재귀적으로 평탄화
return {...acc, ...flattenObject(obj[key], newKey)};
}
// 배열 값은 JSON 문자열로 변환
if (Array.isArray(obj[key])) {
acc[newKey] = JSON.stringify(obj[key]);
} else {
acc[newKey] = obj[key];
}
return acc;
}, {});
};
const flattenedData = flattenObject(detailData);
return (
<TableStyle>
<caption>데이터 상세 정보</caption>
<thead>
<tr>
<th width="120px">필드명</th>
<th width="200px"></th>
</tr>
</thead>
<tbody>
{detailData && Object.entries(flattenedData).map(([key, value], index) => (
<tr key={index}>
<td>{getFieldLabel(key)}</td>
<td>{value !== null && value !== undefined ? String(value) : ''}</td>
</tr>
))}
</tbody>
</TableStyle>
);
};
// changed 배열의 항목들을 테이블로 표시
const renderChangedTable = () => {
if (!changedData || !Array.isArray(changedData)) {
return null;
}
const allChangedItems = [];
changedData.forEach((item, itemIndex) => {
if (item.changed && Array.isArray(item.changed)) {
item.changed.forEach((changedItem) => {
allChangedItems.push({
...changedItem,
// 어떤 데이터 항목에서 온 것인지 구분하기 위한 정보 추가
sourceIndex: itemIndex,
sourceInfo: {
dbType: item.dbType,
timestamp: item.timestamp,
operationType: item.operationType,
historyType: item.historyType,
tableName: item.tableName,
tranId: item.tranId,
userId: item.userId
}
});
});
}
});
if (allChangedItems.length === 0) {
return (
<TableWrapper>
<div style={{ textAlign: 'center', padding: '20px', color: '#666' }}>
변경 항목이 없습니다.
</div>
</TableWrapper>
);
}
// 값 포맷팅 함수
const formatValue = (value) => {
if (value === null || value === undefined) return '';
if (typeof value === 'object') {
if (value.$date) return convertKTC(value.$date,false);
if (value.$oid) return value.$oid;
if (value.$numberLong) return value.$numberLong;
return JSON.stringify(value);
}
return String(value);
};
return (
<TableStyle style={{ minWidth: 'auto', width: '100%', tableLayout: 'fixed' }}>
<caption>변경 항목 목록</caption>
<thead>
<tr>
<th width="60px">유형</th>
<th width="120px">필드명</th>
<th width="150px">신규 </th>
<th width="150px">이전 </th>
<th width="100px">작업자</th>
<th width="130px">작업일시(KST)</th>
</tr>
</thead>
<tbody>
{allChangedItems.map((item, index) => {
if (historyBenField.includes(item.fieldName)) {
return null;
}
return (
<tr key={index}>
<td>{getFieldLabel(item.sourceInfo.operationType)}</td>
<td>{item.fieldName}</td>
<td>{formatValue(item.newValue)}</td>
<td>{formatValue(item.oldValue)}</td>
<td>{item.sourceInfo.userId}</td>
<td>{convertKTC(item.sourceInfo.timestamp, false)}</td>
</tr>
)
})}
</tbody>
</TableStyle>
);
};
// 단일 뷰 또는 탭 뷰 렌더링 결정
const renderContent = () => {
if ((viewMode === 'data' && !detailData) || (viewMode === 'changed' && !changedData)) return null;
// 단일 뷰 모드
if (viewMode === 'data') {
return renderDataTable();
}
if (viewMode === 'changed') {
return renderChangedTable();
}
return (
<>
{/* 탭 메뉴 */}
<TabContainer>
<Tab
$active={activeTab === 'data'}
onClick={() => setActiveTab('data')}
>
데이터 정보
</Tab>
<Tab
$active={activeTab === 'changed'}
onClick={() => setActiveTab('changed')}
>
변경 항목
</Tab>
</TabContainer>
{/* 탭 컨텐츠 */}
<TabContent $active={activeTab === 'data'}>
{renderDataTable()}
</TabContent>
<TabContent $active={activeTab === 'changed'}>
{renderChangedTable()}
</TabContent>
</>
);
};
return (
<>
<Modal $view={detailView} min="400px">
<Title $align="center">{title}</Title>
{renderContent()}
<BtnWrapper2 $justify="center">
<Button
theme="line"
text="확인"
handleClick={e => {
e.preventDefault();
handleDetailView();
}}
/>
</BtnWrapper2>
</Modal>
</>
);
};
export default LogDetailModal;
const BtnWrapper2 = styled(BtnWrapper)`
margin-top: 30px;
`;
const TabContainer = styled.div`
display: flex;
margin: 20px 0 10px;
border-bottom: 1px solid #ddd;
`;

View File

@@ -70,7 +70,16 @@ export const useEnhancedCommonSearch = (configPath) => {
...searchParams,
...newParams
});
} else {
} else if (paginationType === 'front') {
// front 타입은 단순히 originalHandleSearch 호출
// 페이지 정보는 무시하고 모든 데이터 가져오기
const completeParams = {
...searchParams,
...newParams
};
updateSearchParams(completeParams);
return await originalHandleSearch(completeParams, true);
} else{
updateSearchParams(newParams);
return await rdsPagenation.goToPage(1);
}

View File

@@ -4,6 +4,7 @@ import {initReactI18next} from 'react-i18next';
const resources = {
ko: {
translation: {
//메시지
NULL_MSG: '필수값을 입력해주세요.',
DATE_KTC: '* UTC+9 한국시간 기준으로 설정 (UTC+0 자동 반영처리)',
NOT_ITEM: '존재하지 않는 아이템코드입니다.',

View File

@@ -18,14 +18,10 @@ import {
authType,
modalTypes,
} from '../../assets/data';
import { INITIAL_PAGE_LIMIT, INITIAL_PAGE_SIZE } from '../../assets/data/adminConstants';
import { useTranslation } from 'react-i18next';
import {
Button,
DownloadProgress,
DynamicModal,
ExcelDownButton,
Pagination,
TopButton,
ViewTableInfo,
} from '../../components/common';
@@ -33,26 +29,27 @@ import { TableSkeleton } from '../../components/Skeleton/TableSkeleton';
import BusinessLogSearchBar, { useBusinessLogSearch } from '../../components/ServiceManage/searchBar/BusinessLogSearchBar';
import styled from 'styled-components';
import FrontPagination from '../../components/common/Pagination/FrontPagination';
import Loading from '../../components/common/Loading';
import CircularProgress from '../../components/common/CircularProgress';
import VerticalDotsButton from '../../components/common/button/VerticalDotsButton';
import MessageInput from '../../components/common/input/MessageInput';
import { AnalyzeAI } from '../../apis';
// import MessageInput from '../../components/common/input/MessageInput';
// import { AnalyzeAI } from '../../apis';
import {
INITIAL_CURRENT_PAGE,
INITIAL_PAGE_LIMIT,
STORAGE_BUSINESS_LOG_SEARCH,
} from '../../assets/data/adminConstants';
const BusinessLogView = () => {
const token = sessionStorage.getItem('token');
const { t } = useTranslation();
const tableRef = useRef(null);
const [alertMsg, setAlertMsg] = useState('');
const [expandedRows, setExpandedRows] = useState({});
const [downloadState, setDownloadState] = useState({
loading: false,
progress: 0
});
const [currentPage, setCurrentPage] = useState(1);
const [currentPage, setCurrentPage] = useState(INITIAL_CURRENT_PAGE);
const [itemsPerPage, setItemsPerPage] = useState(500);
const [displayData, setDisplayData] = useState([]);
@@ -65,7 +62,25 @@ const BusinessLogView = () => {
handlePageChange,
handleOrderByChange,
updateSearchParams
} = useBusinessLogSearch(token, 500, setAlertMsg);
} = useBusinessLogSearch(token, 500);
useEffect(() => {
// 세션 스토리지에서 복사된 메일 데이터 가져오기
const paramsData = sessionStorage.getItem(STORAGE_BUSINESS_LOG_SEARCH);
if (paramsData) {
const searchData = JSON.parse(paramsData);
handleSearch({
log_action: searchData.action,
start_dt: searchData.start_dt,
end_dt: searchData.end_dt
});
// 사용 후 세션 스토리지 데이터 삭제
sessionStorage.removeItem(STORAGE_BUSINESS_LOG_SEARCH);
}
}, []);
const handlePageSizeChange = (newSize) => {
setItemsPerPage(newSize);
@@ -167,22 +182,14 @@ const BusinessLogView = () => {
);
};
const handleModalSubmit = async (type, param = null) => {
switch (type) {
case "warning":
setAlertMsg('')
break;
}
}
const handleMessage = (message) => {
const params = {}
params.message = message;
params.type = 'BUSINESS_LOG'
params.conditions = searchParams;
AnalyzeAI(token, params);
}
// const handleMessage = (message) => {
// const params = {}
// params.message = message;
// params.type = 'BUSINESS_LOG'
// params.conditions = searchParams;
// AnalyzeAI(token, params);
//
// }
return (
<>
@@ -217,7 +224,7 @@ const BusinessLogView = () => {
</CircularProgressWrapper>
)}
</DownloadContainer>
<MessageInput onSendMessage={handleMessage} />
{/*<MessageInput onSendMessage={handleMessage} />*/}
</ViewTableInfo>
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
<>
@@ -274,19 +281,12 @@ const BusinessLogView = () => {
itemsPerPage={itemsPerPage}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
pageLimit={10}
pageLimit={INITIAL_PAGE_LIMIT}
onPageChange={handleClientPageChange}
/>}
<TopButton />
</>
}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleModalSubmit('warning')}
/>
</>
);
};

View File

@@ -1,4 +1,4 @@
import { useState, Fragment, useRef } from 'react';
import React, { useState, Fragment, useRef } from 'react';
import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next';
import 'react-datepicker/dist/react-datepicker.css';
@@ -41,6 +41,9 @@ import { getDateOnly, getTimeOnly, secondToMinutes } from '../../utils/date';
import { alertTypes, battleEventStatusType } from '../../assets/data/types';
import { useAlert } from '../../context/AlertProvider';
import { useLoading } from '../../context/LoadingProvider';
import LogDetailModal from '../../components/common/modal/LogDetailModal';
import { historyTables } from '../../assets/data/data';
import { LogHistory } from '../../apis';
const BattleEvent = () => {
const token = sessionStorage.getItem('token');
@@ -51,6 +54,7 @@ const BattleEvent = () => {
const {withLoading} = useLoading();
const [detailData, setDetailData] = useState({});
const [historyData, setHistoryData] = useState({});
const {
modalState,
@@ -58,6 +62,7 @@ const BattleEvent = () => {
handleModalClose
} = useModal({
detail: 'hidden',
history: 'hidden'
});
const [modalType, setModalType] = useState('regist');
@@ -123,6 +128,17 @@ const BattleEvent = () => {
const handleModalSubmit = async (type, param = null) => {
switch (type) {
case "history":
const params = {};
params.db_type = "MYSQL"
params.sql_id = param.id;
params.table_name = historyTables.battleEvent
await LogHistory(token, params).then(data => {
setHistoryData(data);
handleModalView('history');
});
break;
case "regist":
setModalType('regist');
handleModalView('detail');
@@ -290,40 +306,41 @@ const BattleEvent = () => {
</thead>
<tbody>
{dataList?.event_list?.map(battle => (
<tr key={battle.row_num}>
<td>
<CheckBox name={'select'} id={battle.id}
setData={(e) => handleSelectRow(e, battle)}
checked={isRowSelected(battle.id)} />
</td>
<td>{battle.group_id}</td>
<td>{battle.event_id}</td>
<td>{battle.event_name}</td>
<StatusWapper>
<StatusLabel $status={battle.repeat_type}>
{battleRepeatType.find(data => data.value === battle.repeat_type).name}
</StatusLabel>
</StatusWapper>
<td>{getDateOnly(convertKTCDate(battle.event_start_dt))}</td>
<td>{getDateOnly(battle.event_end_dt)}</td>
<td>{getTimeOnly(convertKTCDate(battle.event_start_dt))}</td>
<td>{getTimeOnly(endTime(convertKTCDate(battle.event_start_dt), battle.event_operation_time))}</td>
<StatusWapper>
<StatusLabel $status={battle.status}>
{battleEventStatus.find(data => data.value === battle.status).name}
</StatusLabel>
</StatusWapper>
<td>{secondToMinutes(battle.round_time)}</td>
<td>{battle.reward_group_id}</td>
<td>{battle.round_count}</td>
<td>{battle.hot_time}</td>
<td>
<Button theme="line" text="상세보기"
handleClick={e => handleModalSubmit('detail', battle.id)} />
</td>
<td>{battle.update_by}</td>
</tr>
))}
<tr key={battle.row_num}>
<td>
<CheckBox name={'select'} id={battle.id}
setData={(e) => handleSelectRow(e, battle)}
checked={isRowSelected(battle.id)} />
</td>
<td>{battle.group_id}</td>
<td>{battle.event_id}</td>
<td>{battle.event_name}</td>
<StatusWapper>
<StatusLabel $status={battle.repeat_type}>
{battleRepeatType.find(data => data.value === battle.repeat_type).name}
</StatusLabel>
</StatusWapper>
<td>{getDateOnly(convertKTCDate(battle.event_start_dt))}</td>
<td>{getDateOnly(battle.event_end_dt)}</td>
<td>{getTimeOnly(convertKTCDate(battle.event_start_dt))}</td>
<td>{getTimeOnly(endTime(convertKTCDate(battle.event_start_dt), battle.event_operation_time))}</td>
<StatusWapper>
<StatusLabel $status={battle.status}>
{battleEventStatus.find(data => data.value === battle.status).name}
</StatusLabel>
</StatusWapper>
<td>{secondToMinutes(battle.round_time)}</td>
<td>{battle.reward_group_id}</td>
<td>{battle.round_count}</td>
<td>{battle.hot_time}</td>
<td>
<Button theme="line" text="상세보기"
handleClick={e => handleModalSubmit('detail', battle.id)} />
</td>
<td><Button theme="line" text="히스토리"
handleClick={e => handleModalSubmit('history', battle)} /></td>
</tr>
))}
</tbody>
</TableStyle>
</TableWrapper>
@@ -344,6 +361,14 @@ const BattleEvent = () => {
rewardData={battleRewardData}
/>
<LogDetailModal
viewMode="changed"
detailView={modalState.historyModal}
handleDetailView={() => handleModalClose('history')}
changedData={historyData}
title="히스토리"
/>
</>
)
};

View File

@@ -1,4 +1,4 @@
import { Fragment, useState } from 'react';
import React, { Fragment, useState } from 'react';
import { Link } from 'react-router-dom';
import { useEffect } from 'react';
@@ -33,6 +33,9 @@ import { BattleEventDelete, BattleEventDetailView, BattleEventStop } from '../..
import { alertTypes, battleEventStatusType } from '../../assets/data/types';
import { useModal, useTable, withAuth } from '../../hooks/hook';
import { useLoading } from '../../context/LoadingProvider';
import LogDetailModal from '../../components/common/modal/LogDetailModal';
import { historyTables } from '../../assets/data/data';
import { LogHistory } from '../../apis';
const Board = () => {
const token = sessionStorage.getItem('token');
@@ -47,6 +50,15 @@ const Board = () => {
const [registView, setRegistView] = useState('hidden');
const [detailData, setDetailData] = useState('');
const [detailId, setDetailId] = useState('');
const [historyData, setHistoryData] = useState({});
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
history: 'hidden'
});
const {
selectedRows,
@@ -64,6 +76,18 @@ const Board = () => {
const handleModalSubmit = async (type, param = null) => {
switch (type) {
case "history":
const params = {};
params.db_type = "MYSQL"
params.sql_id = param.id;
params.table_name = historyTables.notice
await LogHistory(token, params).then(data => {
setHistoryData(data);
console.log(data);
handleModalView('history');
});
break;
case "detail":
await NoticeDetailView(token, param.id).then(data => {
setDetailData(data);
@@ -136,9 +160,7 @@ const Board = () => {
<th>메시지</th>
<th width="80">송출 횟수</th>
<th width="100">송출 상태</th>
<th width="120">등록자</th>
<th width="200">등록자(이메일주소)</th>
<th width="200">등록일자</th>
<th width="120">히스토리</th>
</tr>
</thead>
<tbody>
@@ -167,9 +189,9 @@ const Board = () => {
<td>
{sendStatus.find(data => data.value === notice.send_status)?.name}
</td>
<td>{notice.create_name}</td>
<td>{notice.create_by}</td>
<td>{convertKTC(notice.create_dt)}</td>
<td><Button theme="line" text="히스토리"
handleClick={e => handleModalSubmit('history', notice)} />
</td>
</tr>
</Fragment>
))}
@@ -187,6 +209,13 @@ const Board = () => {
/>
{/* 인게임 메세지 등록 */}
<BoardRegistModal registView={registView} setRegistView={setRegistView} copyData={isCopyData ? detailData : ''} setIsCopyData={setIsCopyData} userInfo={userInfo} />
<LogDetailModal
viewMode="changed"
detailView={modalState.historyModal}
handleDetailView={() => handleModalClose('history')}
changedData={historyData}
title="히스토리"
/>
</TableWrapper>
</>
);

View File

@@ -1,11 +1,11 @@
import { useState, Fragment } from 'react';
import React, { useState, Fragment } from 'react';
import { useNavigate } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next';
import {
EventDelete,
EventDetailView
EventDetailView, LogHistory,
} from '../../apis';
import { authList } from '../../store/authList';
@@ -33,6 +33,8 @@ 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';
import { historyTables } from '../../assets/data/data';
import LogDetailModal from '../../components/common/modal/LogDetailModal';
const Event = () => {
const token = sessionStorage.getItem('token');
@@ -43,6 +45,7 @@ const Event = () => {
const navigate = useNavigate();
const [detailData, setDetailData] = useState('');
const [historyData, setHistoryData] = useState({});
const {
modalState,
@@ -51,6 +54,7 @@ const Event = () => {
} = useModal({
detail: 'hidden',
delete: 'hidden',
history: 'hidden'
});
const [deleteDesc, setDeleteDesc] = useState('');
@@ -85,6 +89,17 @@ const Event = () => {
const handleModalSubmit = async (type, param = null) => {
switch (type) {
case "history":
const params = {};
params.db_type = "MYSQL"
params.sql_id = param.id;
params.table_name = historyTables.event
await LogHistory(token, params).then(data => {
setHistoryData(data);
handleModalView('history');
});
break;
case "delete":
const delete_check = selectedRows.every(row => {
const timeDiff = timeDiffMinute(convertKTC(row.start_dt), (new Date));
@@ -188,7 +203,7 @@ const Event = () => {
<th width="210">종료 일시</th>
<th>우편 제목</th>
<th width="110">확인 / 수정</th>
<th width="200">등록자(처리자)</th>
<th width="200">히스토리</th>
</tr>
</thead>
<tbody>
@@ -213,7 +228,9 @@ const Event = () => {
<Button theme="line" text="상세보기"
handleClick={e => handleDetailModal(e, event.id)} />
</td>
<td>{event.create_by}</td>
<td><Button theme="line" text="히스토리"
handleClick={e => handleModalSubmit('history', event)} />
</td>
</tr>
</Fragment>
))}
@@ -234,6 +251,14 @@ const Event = () => {
setDetailData={setDetailData}
/>
<LogDetailModal
viewMode="changed"
detailView={modalState.historyModal}
handleDetailView={() => handleModalClose('history')}
changedData={historyData}
title="히스토리"
/>
<DynamicModal
modalType={modalTypes.childOkCancel}
view={modalState.deleteModal}

View File

@@ -1,4 +1,4 @@
import { useState, Fragment, useRef } from 'react';
import React, { useState, Fragment, useRef } from 'react';
import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next';
import 'react-datepicker/dist/react-datepicker.css';
@@ -6,7 +6,7 @@ import 'react-datepicker/dist/react-datepicker.css';
import {
BuildingView,
LandAuctionDelete,
LandAuctionDetailView, LandView,
LandAuctionDetailView, LandView, LogHistory,
} from '../../apis';
import { authList } from '../../store/authList';
@@ -34,6 +34,8 @@ import { useLandAuctionSearch } from '../../components/ServiceManage/searchBar/L
import { StatusWapper, ChargeBtn, StatusLabel } from '../../styles/ModuleComponents';
import { alertTypes } from '../../assets/data/types';
import { useAlert } from '../../context/AlertProvider';
import LogDetailModal from '../../components/common/modal/LogDetailModal';
import { historyTables } from '../../assets/data/data';
const LandAuction = () => {
const token = sessionStorage.getItem('token');
@@ -43,6 +45,7 @@ const LandAuction = () => {
const { showToast, showModal } = useAlert();
const [detailData, setDetailData] = useState({});
const [historyData, setHistoryData] = useState({});
const {
modalState,
@@ -50,6 +53,7 @@ const LandAuction = () => {
handleModalClose
} = useModal({
detail: 'hidden',
history: 'hidden'
});
const [alertMsg, setAlertMsg] = useState('');
const [modalType, setModalType] = useState('regist');
@@ -81,6 +85,17 @@ const LandAuction = () => {
const handleModalSubmit = async (type, param = null) => {
switch (type) {
case "history":
const params = {};
params.db_type = "MYSQL"
params.sql_id = param.id;
params.table_name = historyTables.landAuction
await LogHistory(token, params).then(data => {
setHistoryData(data);
handleModalView('history');
});
break;
case "regist":
setModalType('regist');
handleModalView('detail');
@@ -230,7 +245,8 @@ const LandAuction = () => {
<Button theme="line" text="상세보기"
handleClick={e => handleModalSubmit('detail', auction.id)} />
</td>
<td>{auction.update_by}</td>
<td><Button theme="line" text="히스토리"
handleClick={e => handleModalSubmit('history', auction)} /></td>
</tr>
))}
</tbody>
@@ -253,6 +269,14 @@ const LandAuction = () => {
buildingData={buildingData}
/>
<LogDetailModal
viewMode="changed"
detailView={modalState.historyModal}
handleDetailView={() => handleModalClose('history')}
changedData={historyData}
title="히스토리"
/>
</>
)
};

View File

@@ -1,9 +1,9 @@
import styled from 'styled-components';
import { useState, Fragment } from 'react';
import React, { useState, Fragment } from 'react';
import CheckBox from '../../components/common/input/CheckBox';
import { MailDelete, MailDetailView } from '../../apis';
import { LogHistory, MailDelete, MailDetailView } from '../../apis';
import { Title, FormWrapper, TableStyle, MailTitle, TableWrapper } from '../../styles/Components';
import Button from '../../components/common/button/Button';
@@ -32,6 +32,8 @@ import { useModal, useTable, withAuth } from '../../hooks/hook';
import { useAlert } from '../../context/AlertProvider';
import { useLoading } from '../../context/LoadingProvider';
import useCommonSearchOld from '../../hooks/useCommonSearchOld';
import LogDetailModal from '../../components/common/modal/LogDetailModal';
import { historyTables } from '../../assets/data/data';
const Mail = () => {
const token = sessionStorage.getItem('token');
@@ -41,6 +43,7 @@ const Mail = () => {
const navigate = useNavigate();
const [detailData, setDetailData] = useState('');
const [historyData, setHistoryData] = useState({});
const {
modalState,
@@ -48,6 +51,7 @@ const Mail = () => {
handleModalClose
} = useModal({
detail: 'hidden',
history: 'hidden'
});
const {
@@ -71,6 +75,17 @@ const Mail = () => {
const handleModalSubmit = async (type, param = null) => {
switch (type) {
case "history":
const params = {};
params.db_type = "MYSQL"
params.sql_id = param.id;
params.table_name = historyTables.mail
await LogHistory(token, params).then(data => {
setHistoryData(data);
handleModalView('history');
});
break;
case "detail":
await MailDetailView(token, param).then(data => {
setDetailData(data);
@@ -167,7 +182,7 @@ const Mail = () => {
<th width="100">수신 대상</th>
<th>우편 제목</th>
<th width="110">확인 / 수정</th>
<th width="200">등록자(처리자)</th>
<th width="200">히스토리</th>
</tr>
</thead>
<tbody>
@@ -192,12 +207,15 @@ const Mail = () => {
<td>{mailReceiveType.map(data => data.value === mail.receive_type && data.name)}</td>
<MailTitle>{mail.title}</MailTitle>
<td>
<Button theme="line" text="상세보기" handleClick={e => handleModalSubmit('detail', mail.id)} />
<Button theme="line" text="상세보기"
handleClick={e => handleModalSubmit('detail', mail.id)} />
</td>
<td><Button theme="line" text="히스토리"
handleClick={e => handleModalSubmit('history', mail)} />
</td>
<td>{mail.create_by}</td>
</tr>
</Fragment>
))}
))}
</tbody>
</TableStyle>
</TableWrapper>
@@ -212,6 +230,14 @@ const Mail = () => {
content={detailData}
/>
<LogDetailModal
viewMode="changed"
detailView={modalState.historyModal}
handleDetailView={() => handleModalClose('history')}
changedData={historyData}
title="히스토리"
/>
</>
);
};

View File

@@ -36,6 +36,7 @@ import { alertTypes, currencyCodeTypes } from '../../assets/data/types';
import { useLoading } from '../../context/LoadingProvider';
import { useAlert } from '../../context/AlertProvider';
import { userType2 } from '../../assets/data/options';
import { STORAGE_MAIL_COPY } from '../../assets/data/adminConstants';
const MailRegist = () => {
const navigate = useNavigate();
@@ -49,7 +50,7 @@ const MailRegist = () => {
const [sendMin, setSendMin] = useState('00');
const [item, setItem] = useState('');
const [itemCount, setItemCount] = useState('');
const [itemCount, setItemCount] = useState('1');
const [resource, setResource] = useState(currencyCodeTypes.gold);
const [resourceCount, setResourceCount] = useState(0);
@@ -95,7 +96,7 @@ const MailRegist = () => {
useEffect(() => {
// 세션 스토리지에서 복사된 메일 데이터 가져오기
const copiedMailData = sessionStorage.getItem('copyMailData');
const copiedMailData = sessionStorage.getItem(STORAGE_MAIL_COPY);
if (copiedMailData) {
const mailData = JSON.parse(copiedMailData);
@@ -118,7 +119,7 @@ const MailRegist = () => {
}
// 사용 후 세션 스토리지 데이터 삭제
sessionStorage.removeItem('copyMailData');
sessionStorage.removeItem(STORAGE_MAIL_COPY);
}
}, []);
@@ -161,14 +162,14 @@ const MailRegist = () => {
setIsItemNullValue(true);
} else if (item.length !== 0) {
setIsItemNullValue(false);
const itemIndex = resultData.item_list.findIndex(
data => data.item === item
);
if (itemIndex !== -1) {
showToast('MAIL_ITEM_ADD_DUPL', { type: alertTypes.warning });
return;
}
// const itemIndex = resultData.item_list.findIndex(
// data => data.item === item
// );
//
// if (itemIndex !== -1) {
// showToast('MAIL_ITEM_ADD_DUPL', { type: alertTypes.warning });
// return;
// }
const newItem = { item: item, item_cnt: itemCount, item_name: data.data.item_info.item_name };
resultData.item_list.push(newItem);

View File

@@ -0,0 +1,433 @@
import React, { useState, Fragment, useEffect } from 'react';
import styled from 'styled-components';
import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next';
import Button from '../../components/common/button/Button';
import Loading from '../../components/common/Loading';
import {
Title,
BtnWrapper,
SearchBarAlert,
} from '../../styles/Components';
import { useNavigate } from 'react-router-dom';
import { MenuBannerSingleRegist } from '../../apis';
import { authList } from '../../store/authList';
import {
FormInput, FormInputSuffix, FormInputSuffixWrapper, FormLabel, FormRowGroup,RegistGroup,
} from '../../styles/ModuleComponents';
import AuthModal from '../../components/common/modal/AuthModal';
import { authType, modalTypes } from '../../assets/data';
import DynamicModal from '../../components/common/modal/DynamicModal';
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';
const MenuBannerRegist = () => {
const navigate = useNavigate();
const userInfo = useRecoilValue(authList);
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); //데이터 정보
const [resetDateTime, setResetDateTime] = useState(false);
const [pageConfig, setPageConfig] = useState(null);
const [formData, setFormData] = useState({});
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();
}, []);
useEffect(() => {
if (checkCondition()) {
setIsNullValue(false);
} else {
setIsNullValue(true);
}
}, [resultData]);
useEffect(() => {
if (resetDateTime) {
setResetDateTime(false);
}
}, [resetDateTime]);
// 시작 날짜 변경 핸들러
const handleStartDateChange = (date) => {
if (!date) return;
const newDate = new Date(date);
if(resultData.end_dt){
const endDate = new Date(resultData.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,
start_dt: newDate
}));
};
// 시작 시간 변경 핸들러
const handleStartTimeChange = (time) => {
if (!time) return;
const newDateTime = resultData.start_dt
? new Date(resultData.start_dt)
: new Date();
newDateTime.setHours(
time.getHours(),
time.getMinutes(),
0,
0
);
setResultData(prev => ({
...prev,
start_dt: newDateTime
}));
};
// 종료 날짜 변경 핸들러
const handleEndDateChange = (date) => {
if (!date || !resultData.start_dt) return;
const startDate = new Date(resultData.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,
end_dt: endDate
}));
};
// 종료 시간 변경 핸들러
const handleEndTimeChange = (time) => {
if (!time) return;
const newDateTime = resultData.end_dt
? new Date(resultData.end_dt)
: new Date();
newDateTime.setHours(
time.getHours(),
time.getMinutes(),
0,
0
);
setResultData(prev => ({
...prev,
end_dt: newDateTime
}));
};
// 이미지 업로드
const handleImageUpload = (language, file, fileName) => {
const imageIndex = resultData.image_list.findIndex(img => img.language === language);
if (imageIndex !== -1) {
const updatedImageList = [...resultData.image_list];
updatedImageList[imageIndex] = {
...updatedImageList[imageIndex],
content: fileName,
};
setResultData({
...resultData,
image_list: updatedImageList
});
}
};
// 이미지 삭제
const handleImageDelete = (language) => {
const imageIndex = resultData.image_list.findIndex(img => img.language === language);
if (imageIndex !== -1) {
const updatedImageList = [...resultData.image_list];
updatedImageList[imageIndex] = {
...updatedImageList[imageIndex],
content: '',
};
setResultData({
...resultData,
image_list: updatedImageList
});
}
};
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":
setLoading(true);
const result = await MenuBannerSingleRegist(token, resultData);
setLoading(false);
handleModalClose('registConfirm');
handleModalView('registComplete');
break;
case "registComplete":
handleModalClose('registComplete');
navigate('/servicemanage/menubanner');
break;
case "warning":
setAlertMsg('');
break;
}
}
const checkCondition = () => {
return (
(resultData.start_dt.length !== 0) &&
(resultData.end_dt.length !== 0) &&
resultData.title !== '' &&
resultData.image_list.every(data => data.content !== '') &&
(resultData.is_link === false || (resultData.is_link === true && resultData.link_list.every(data => data.content !== '')))
);
};
return (
<>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) ? (
<AuthModal/>
) : (
<>
<Title>메뉴배너 등록</Title>
<RegistGroup>
<FormRowGroup>
<FormLabel>등록기간</FormLabel>
<SingleDatePicker
label="시작일자"
dateLabel="시작 일자"
onDateChange={handleStartDateChange}
selectedDate={resultData?.start_dt}
/>
<SingleTimePicker
selectedTime={resultData?.start_dt}
onTimeChange={handleStartTimeChange}
/>
<SingleDatePicker
label="종료일자"
dateLabel="종료 일자"
onDateChange={handleEndDateChange}
selectedDate={resultData?.end_dt}
/>
<SingleTimePicker
selectedTime={resultData?.end_dt}
onTimeChange={handleEndTimeChange}
/>
</FormRowGroup>
<FormRowGroup>
<FormLabel>배너 제목</FormLabel>
<FormInput
type="text"
width='50%'
value={resultData?.title}
onChange={e => setResultData({ ...resultData, title: e.target.value })}
/>
</FormRowGroup>
<FormLabel>이미지 첨부</FormLabel>
{resultData.image_list.map((data, idx) => (
<LanguageWrapper key={idx}>
<LanguageLabel>{data.language}</LanguageLabel>
<ImageUploadBtn
onImageUpload={(file, fileName) => handleImageUpload(data.language, file, fileName)}
onFileDelete={() => handleImageDelete(data.language)}
fileName={data.content}
setAlertMessage={setAlertMsg}
/>
</LanguageWrapper>
))}
<FormRowGroup>
<CheckBox
label="이미지 링크 여부"
id="reserve"
checked={resultData.is_link}
setData={e => setResultData({ ...resultData, is_link: e.target.checked })}
/>
</FormRowGroup>
{resultData?.is_link &&
<>
<FormRowGroup>
<FormLabel> 링크</FormLabel>
<LanguageWrapper width="50%" >
{resultData.link_list.map((data, idx) => (
<FormInputSuffixWrapper>
<FormInput
type="text"
value={resultData?.link_list[idx].content}
onChange={e => {
const updatedLinkList = [...resultData.link_list];
updatedLinkList[idx] = { ...updatedLinkList[idx], content: e.target.value };
setResultData({ ...resultData, link_list: updatedLinkList });
}}
suffix="true"
/>
<FormInputSuffix>{data.language}</FormInputSuffix>
</FormInputSuffixWrapper>
))}
</LanguageWrapper>
</FormRowGroup>
</>
}
</RegistGroup>
{isNullValue && (
<SearchBarAlert $align="right" $padding="0 0 15px">
{t('NULL_MSG')}
</SearchBarAlert>
)}
<BtnWrapper $justify="flex-end" $gap="10px">
<Button text="취소" theme="line" handleClick={() => handleModalView('cancel')} />
<Button
type="submit"
text="등록"
theme={checkCondition() ? 'primary' : 'disable'}
handleClick={() => handleSubmit('submit')}
/>
</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/>}
</>
)}
</>
);
};
const initData = {
title: '',
is_link: false,
start_dt: '',
end_dt: '',
image_list: [
{ language: 'KO', content: '' },
{ language: 'EN', content: '' },
{ language: 'JA', content: '' },
],
link_list: [
{ language: 'KO', content: '' },
{ language: 'EN', content: '' },
{ language: 'JA', content: '' },
],
}
export default MenuBannerRegist;
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;
`;

View File

@@ -0,0 +1,341 @@
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 {
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 { 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'
const MenuBanner = () => {
const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const tableRef = useRef(null);
const navigate = useNavigate();
const [detailData, setDetailData] = useState({});
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
detail: 'hidden',
deleteConfirm: 'hidden',
deleteComplete: 'hidden'
});
const [alertMsg, setAlertMsg] = useState('');
const [modalType, setModalType] = useState('regist');
const {
searchParams,
data: dataList,
handleSearch,
handleReset,
handlePageChange,
handlePageSizeChange,
handleOrderByChange,
updateSearchParams
} = useMenuBannerSearch(token, INITIAL_PAGE_SIZE);
const {
selectedRows,
handleSelectRow,
isRowSelected
} = useTable(dataList?.event_list || [], {mode: 'single'});
const handleModalSubmit = async (type, param = null) => {
switch (type) {
case "regist":
setModalType('regist');
handleModalView('detail');
break;
case "detail":
await MenuBannerDetailView(token, param).then(data => {
setDetailData(data.event_detail);
setModalType('modify');
handleModalView('detail');
});
break;
case "delete":
const date_check = selectedRows.every(row => {
const timeDiff = timeDiffMinute(convertKTC(row.auction_start_dt), (new Date));
return timeDiff < 3;
});
if(date_check){
setAlertMsg(t('LAND_AUCTION_DELETE_DATE_WARNING'));
return;
}
if(selectedRows[0].status === landAuctionStatusType.auction_start || selectedRows[0].status === landAuctionStatusType.stl_end){
setAlertMsg(t('LAND_AUCTION_DELETE_STATUS_WARNING'));
return;
}
handleModalView('deleteConfirm');
break;
case "deleteConfirm":
let list = [];
let isChecked = false;
selectedRows.map(data => {
// const row = dataList.list.find(row => row.id === Number(data.id));
// if(row.status !== commonStatus.wait) isChecked = true;
list.push({
id: data.id,
});
});
if(isChecked) {
setAlertMsg(t('LAND_AUCTION_WARNING_DELETE'))
handleModalClose('deleteConfirm');
return;
}
await MenuBannerDelete(token, list).then(data => {
handleModalClose('deleteConfirm');
if(data.result === "SUCCESS") {
handleModalView('deleteComplete');
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){
setAlertMsg(t('LAND_AUCTION_ERROR_DELETE_STATUS'));
}else{
setAlertMsg(t('DELETE_FAIL'));
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
});
break;
case "deleteComplete":
handleModalClose('deleteComplete');
window.location.reload();
break;
case "warning":
setAlertMsg('')
break;
}
}
const handleAction = async (action, item = null) => {
switch (action) {
case "regist":
setModalType('regist');
handleModalView('detail');
break;
case "detail":
await MenuBannerDetailView(token, item).then(data => {
setDetailData(data.event_detail);
setModalType('modify');
handleModalView('detail');
});
break;
case "delete":
const date_check = selectedRows.every(row => {
const timeDiff = timeDiffMinute(convertKTC(row.auction_start_dt), (new Date));
return timeDiff < 3;
});
if(date_check){
setAlertMsg(t('LAND_AUCTION_DELETE_DATE_WARNING'));
return;
}
if(selectedRows[0].status === landAuctionStatusType.auction_start || selectedRows[0].status === landAuctionStatusType.stl_end){
setAlertMsg(t('LAND_AUCTION_DELETE_STATUS_WARNING'));
return;
}
handleModalView('deleteConfirm');
break;
case "deleteConfirm":
let list = [];
let isChecked = false;
selectedRows.map(data => {
// const row = dataList.list.find(row => row.id === Number(data.id));
// if(row.status !== commonStatus.wait) isChecked = true;
list.push({
id: data.id,
});
});
if(isChecked) {
setAlertMsg(t('LAND_AUCTION_WARNING_DELETE'))
handleModalClose('deleteConfirm');
return;
}
await MenuBannerDelete(token, list).then(data => {
handleModalClose('deleteConfirm');
if(data.result === "SUCCESS") {
handleModalView('deleteComplete');
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){
setAlertMsg(t('LAND_AUCTION_ERROR_DELETE_STATUS'));
}else{
setAlertMsg(t('DELETE_FAIL'));
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
});
break;
case "deleteComplete":
handleModalClose('deleteComplete');
window.location.reload();
break;
case "warning":
setAlertMsg('')
break;
default:
break;
}
};
return (
<>
<Title>메뉴 배너 관리</Title>
<FormWrapper>
<MenuBannerSearchBar
searchParams={searchParams}
onSearch={(newParams, executeSearch = true) => {
if (executeSearch) {
handleSearch(newParams);
} else {
updateSearchParams(newParams);
}
}}
onReset={handleReset}
/>
</FormWrapper>
{/*<ViewTableInfo total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>*/}
{/* {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>*/}
<TableHeader
config={tableInfo.header}
total={dataList?.total}
total_all={dataList?.total_all}
handleOrderBy={handleOrderByChange}
handlePageSize={handlePageSizeChange}
selectedRows={selectedRows}
onAction={handleAction}
navigate={navigate}
/>
<CaliTable
columns={tableInfo.columns}
data={dataList?.list}
selectedRows={selectedRows}
onSelectRow={handleSelectRow}
onAction={handleAction}
refProp={tableRef}
/>
{/*<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} />
{/*상세*/}
<MenuBannerModal modalType={modalType} detailView={modalState.detailModal} handleDetailView={() => handleModalClose('detail')} content={detailData} setDetailData={setDetailData} />
{/*삭제 확인*/}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.deleteConfirmModal}
handleCancel={() => handleModalClose('deleteConfirm')}
handleSubmit={() => handleModalSubmit('deleteConfirm')}
modalText={t('MENU_BANNER_SELECT_DELETE')}
/>
{/*삭제 완료*/}
<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')}
/>
</>
)
};
export default withAuth(authType.battleEventRead)(MenuBanner);

View File

@@ -1,12 +1,11 @@
import { useState, Fragment, useRef, useTransition } from 'react';
import React, { useState, Fragment } from 'react';
import { Title, FormWrapper, TextInput } from '../../styles/Components';
import { useNavigate } from 'react-router-dom';
import {
CommonSearchBar,
useCommonSearch,
UserBlockDetailModal,
} from '../../components/ServiceManage/';
import { BlackListDetail, BlackListDelete } from '../../apis';
import { BlackListDetail, BlackListDelete, LogHistory } from '../../apis';
import Pagination from '../../components/common/Pagination/Pagination';
import { authType, commonStatus, modalTypes } from '../../assets/data';
@@ -21,6 +20,8 @@ import useCommonSearchOld from '../../hooks/useCommonSearchOld';
import { ModalInputItem, ModalSubText, RegistInputItem } from '../../styles/ModuleComponents';
import DynamicModal from '../../components/common/modal/DynamicModal';
import { useTranslation } from 'react-i18next';
import LogDetailModal from '../../components/common/modal/LogDetailModal';
import { historyTables } from '../../assets/data/data';
const UserBlock = () => {
const token = sessionStorage.getItem('token');
@@ -31,13 +32,15 @@ const UserBlock = () => {
const [detail, setDetail] = useState([]);
const [deleteDesc, setDeleteDesc] = useState('');
const [historyData, setHistoryData] = useState({});
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
detail: 'hidden',
delete: 'hidden'
delete: 'hidden',
history: 'hidden'
});
const {
@@ -62,6 +65,17 @@ const UserBlock = () => {
const handleAction = async (action, item = null) => {
switch (action) {
case "history":
const params = {};
params.db_type = "MYSQL"
params.sql_id = item.id;
params.table_name = historyTables.userBlock
await LogHistory(token, params).then(data => {
setHistoryData(data);
handleModalView('history');
});
break;
case "detail":
await BlackListDetail(token, item.id).then(data => {
setDetail(data);
@@ -165,6 +179,14 @@ const UserBlock = () => {
handleModal={() => handleModalClose('detail')}
/>
<LogDetailModal
viewMode="changed"
detailView={modalState.historyModal}
handleDetailView={() => handleModalClose('history')}
changedData={historyData}
title="히스토리"
/>
<DynamicModal
modalType={modalTypes.childOkCancel}
view={modalState.deleteModal}

View File

@@ -1,4 +1,4 @@
import { useState, Fragment, useRef } from 'react';
import React, { useState, Fragment, useRef } from 'react';
import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next';
import 'react-datepicker/dist/react-datepicker.css';
@@ -14,19 +14,27 @@ import { Title, FormWrapper, TableStyle, TableWrapper, PopupMessage } from '../.
import {
StatusWapper,
ChargeBtn,
StatusLabel,
StatusLabel, TitleItemLabel, TitleItemValue, TitleItem,
} from '../../styles/ModuleComponents';
import {Button, ExcelDownButton, Pagination, ViewTableInfo} from '../../components/common';
import { convertKTC, truncateText } from '../../utils';
import { CaliumRequestRegistModal } from '../../components/UserManage';
import { CaliumCharge } from '../../apis';
import { CaliumCharge, LogHistory } from '../../apis';
import { useModal, withAuth } from '../../hooks/hook';
import { CommonSearchBar, useCommonSearch } from '../../components/ServiceManage';
import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
import {
INITIAL_PAGE_LIMIT,
LOG_ACTION_FAIL_CALIUM_ECHO,
STORAGE_BUSINESS_LOG_SEARCH,
} 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';
import LogDetailModal from '../../components/common/modal/LogDetailModal';
import { historyTables } from '../../assets/data/data';
import { useNavigate } from 'react-router-dom';
import { logAction } from '../../assets/data/options';
const CaliumRequest = () => {
const token = sessionStorage.getItem('token');
@@ -34,9 +42,11 @@ const CaliumRequest = () => {
const { t } = useTranslation();
const { showModal, showToast } = useAlert();
const {withLoading} = useLoading();
const navigate = useNavigate();
const tableRef = useRef(null);
const [selectCharge, setSelectCharge] = useState({});
const [historyData, setHistoryData] = useState({});
const {
modalState,
@@ -44,6 +54,7 @@ const CaliumRequest = () => {
handleModalClose
} = useModal({
register: 'hidden',
history: 'hidden'
});
const {
@@ -61,6 +72,17 @@ const CaliumRequest = () => {
const handleSubmit = async (type, param = null) => {
switch (type) {
case "history":
const params = {};
params.db_type = "MYSQL"
params.sql_id = param.id;
params.table_name = historyTables.caliumRequest
await LogHistory(token, params).then(data => {
setHistoryData(data);
handleModalView('history');
});
break;
case "detail":
showModal(param, {
type: alertTypes.info
@@ -91,6 +113,25 @@ const CaliumRequest = () => {
handleSearch(updateSearchParams);
});
break;
case "logMove":
const searchParams = {
action: LOG_ACTION_FAIL_CALIUM_ECHO,
start_dt: (() => {
const date = new Date();
date.setDate(date.getDate() - 1);
return date;
})(),
end_dt: (() => {
const date = new Date();
date.setDate(date.getDate() - 1);
return date;
})(),
};
// 복사한 데이터를 세션 스토리지에 저장
sessionStorage.setItem(STORAGE_BUSINESS_LOG_SEARCH, JSON.stringify(searchParams));
navigate('/datamanage/businesslogview');
break;
}
}
@@ -111,7 +152,15 @@ const CaliumRequest = () => {
onReset={handleReset}
/>
</FormWrapper>
<ViewTableInfo total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange} countType={ViewTitleCountType.calium}>
<ViewTableInfo
total={dataList?.total}
total_all={dataList?.total_all}
fail_count={dataList?.failCount}
handleOrderBy={handleOrderByChange}
handlePageSize={handlePageSizeChange}
countType={ViewTitleCountType.calium}
onFailCountClick={() => handleSubmit('logMove')}
>
<ExcelDownButton tableRef={tableRef} fileName={t('FILE_CALIUM_REQUEST')} />
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) && (
<Button
@@ -138,6 +187,7 @@ const CaliumRequest = () => {
<th width="80">요청수량</th>
<th width="160">진행상태</th>
<th width="210">최종 처리일시</th>
<th width="200">히스토리</th>
</tr>
</thead>
<tbody>
@@ -155,15 +205,22 @@ const CaliumRequest = () => {
</td>
<td>{calium.count}</td>
<StatusWapper>
<StatusLabel $status={calium.status}>{caliumStatus.map(data => data.value === calium.status && data.name)}</StatusLabel>
<StatusLabel
$status={calium.status}>{caliumStatus.map(data => data.value === calium.status && data.name)}</StatusLabel>
{calium.status === commonStatus.complete && userInfo.auth_list?.some(auth => auth.id === authType.eventUpdate) && calium.create_by === userInfo.name &&
<ChargeBtn onClick={() => handleSubmit('chargedConfirm', {id: calium.id, count: calium.count})}>충전</ChargeBtn>
<ChargeBtn onClick={() => handleSubmit('chargedConfirm', {
id: calium.id,
count: calium.count,
})}>충전</ChargeBtn>
}
</StatusWapper>
<td>{convertKTC(calium.state_time)}</td>
<td><Button theme="line" text="히스토리"
handleClick={e => handleSubmit('history', calium)} />
</td>
</tr>
</Fragment>
))}
))}
</tbody>
</TableStyle>
</TableWrapper>
@@ -185,6 +242,14 @@ const CaliumRequest = () => {
userInfo={userInfo}
/>
<LogDetailModal
viewMode="changed"
detailView={modalState.historyModal}
handleDetailView={() => handleModalClose('history')}
changedData={historyData}
title="히스토리"
/>
</>
);
};

View File

@@ -28,12 +28,16 @@ import { DynamicModal, TopButton } from '../../components/common';
import { opInitDataType, opSuccessType } from '../../assets/data/options';
import { InitData } from '../../apis/Data';
import DataInitSearchBar, { useDataInitSearch } from '../../components/ServiceManage/searchBar/DataInitSearchBar';
import { useAlert } from '../../context/AlertProvider';
import { useLoading } from '../../context/LoadingProvider';
import { alertTypes } from '../../assets/data/types';
const DataInitView = () => {
const { t } = useTranslation();
const token = sessionStorage.getItem('token');
const {showModal, showToast} = useAlert();
const {withLoading} = useLoading();
const [loading, setLoading] = useState(false); // 로딩 창
const {
modalState,
handleModalView,
@@ -44,8 +48,6 @@ const DataInitView = () => {
registComplete: 'hidden'
});
const [alertMsg, setAlertMsg] = useState('');
const [resultData, setResultData] = useState(initData); //데이터 정보
const [expandedRows, setExpandedRows] = useState({});
@@ -56,7 +58,7 @@ const DataInitView = () => {
handleSearch,
handleReset,
updateSearchParams
} = useDataInitSearch(token, setAlertMsg);
} = useDataInitSearch(token);
const toggleRowExpand = (index) => {
setExpandedRows(prev => ({
@@ -111,29 +113,23 @@ const DataInitView = () => {
handleModalView('registConfirm');
break;
case "registConfirm":
setLoading(true);
await InitData(token, resultData).then(data => {
await withLoading(async () => {
return await InitData(token, resultData);
}).then(data => {
handleModalClose('registConfirm');
if(data.result === "SUCCESS") {
handleModalView('registComplete');
showToast('INIT_COMPLTE', {type: alertTypes.success});
}else{
setAlertMsg(t('REGIST_FAIL'));
showToast('REGIST_FAIL', {type: alertTypes.error});
}
}).catch(reason => {
handleModalClose('registConfirm');
setAlertMsg(t('API_FAIL'));
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
setLoading(false);
dataReset();
});
break;
case "registComplete":
dataReset();
handleModalClose('registComplete');
break;
case "warning":
setAlertMsg('');
break;
}
}
@@ -192,11 +188,11 @@ const DataInitView = () => {
<Fragment key={index}>
<tr>
<td>{item.timestamp}</td>
<td>{item.key}</td>
<td>{opInitDataType.find(type => type.value === item.initDataType)?.name}</td>
<td>{item.body.key}</td>
<td>{opInitDataType.find(type => type.value === item.body.initDataType)?.name}</td>
<td>{item.tranId}</td>
<td>{opSuccessType.find(type => type.value === item.success)?.name}</td>
<td>{item.message}</td>
<td>{opSuccessType.find(type => type.value === item.body.success)?.name}</td>
<td>{item.body.message}</td>
<td>
<TableActionButton onClick={() => toggleRowExpand(index)}>
{expandedRows[index] ? '접기' : '상세보기'}
@@ -209,7 +205,7 @@ const DataInitView = () => {
<TableDetailContainer>
<TableDetailFlex>
<TableDetailColumn>
{renderDetailData(item.data)}
{renderDetailData(item.body.data)}
</TableDetailColumn>
</TableDetailFlex>
</TableDetailContainer>
@@ -230,21 +226,6 @@ const DataInitView = () => {
handleSubmit={() => handleSubmit('registConfirm')}
handleCancel={() => handleModalClose('registConfirm')}
/>
{/* 완료 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.registCompleteModal}
modalText={t('INIT_COMPLTE')}
handleSubmit={() => handleSubmit('registComplete')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => setAlertMsg('')}
/>
{(loading || dataLoading) && <Loading/>}
<TopButton />
</>
);

View File

@@ -1,170 +1,106 @@
import { Fragment, useState, useEffect } from 'react';
import React, { Fragment, useCallback, useState } from 'react';
import {LogViewSearchBar} from '../../components/ServiceManage';
import { Title, FormWrapper, SelectInput, TableInfo, ListCount, ListOption, TableStyle, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
import Button from '../../components/common/button/Button';
import Pagination from '../../components/common/Pagination/Pagination';
import Modal from '../../components/common/modal/Modal';
import LogViewModal from '../../components/UserManage/LogViewModal';
import { LogViewList } from '../../apis';
import { useNavigate } from 'react-router-dom';
import { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil';
import { logOption } from '../../assets/data';
import { convertKTC } from '../../utils';
import { CommonSearchBar } from '../../components/ServiceManage';
import { Title, FormWrapper } from '../../styles/Components';
import { authType } from '../../assets/data';
import { useModal, withAuth } from '../../hooks/hook';
import { CaliTable } from '../../components/common';
import tableInfo from '../../assets/data/pages/historyTable.json';
import useEnhancedCommonSearch from '../../hooks/useEnhancedCommonSearch';
import FrontPagination from '../../components/common/Pagination/FrontPagination';
import { INITIAL_CURRENT_PAGE, INITIAL_PAGE_LIMIT, INITIAL_PAGE_SIZE } from '../../assets/data/adminConstants';
import LogDetailModal from '../../components/common/modal/LogDetailModal';
const LogView = () => {
const navigate = useNavigate();
const userInfo = useRecoilValue(authList);
const [detailData, setDetailData] = useState({});
const [currentPage, setCurrentPage] = useState(INITIAL_CURRENT_PAGE);
const [displayData, setDisplayData] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
detail: 'hidden',
});
const [stateModal, setStateModal] = useState('hidden');
const [dataList, setDataList] = useState([]);
const [detailData, setDetailData] = useState('');
const [searchData, setSearchData] = useState({});
const [orderBy, setOrderBy] = useState('DESC');
const [pageSize, setPageSize] = useState('50');
const {
config,
searchParams,
data: dataList,
handleSearch,
handleReset,
updateSearchParams,
loading
} = useEnhancedCommonSearch("historySearch");
const handleButtonClick = content => {
setStateModal('view');
setDetailData(content);
};
const handleClientPageChange = useCallback((slicedData) => {
setDisplayData(slicedData);
}, []);
const handleModal = () => {
if (stateModal === 'hidden') {
setStateModal('view');
} else {
setStateModal('hidden');
const handleAction = async (action, item = null) => {
switch (action) {
case "detail":
handleModalView('detail');
setDetailData(item.data);
break;
default:
break;
}
};
const handleOrderBy = e => {
const order = e.target.value;
setOrderBy(order);
fetchData(
searchData.searchOption,
searchData.data,
searchData.logOption,
searchData.startDate && new Date(searchData.startDate).toISOString(),
searchData.endDate && new Date(searchData.endDate).toISOString(),
order,
pageSize,
);
};
const handlePageSize = e => {
const size = e.target.value;
setPageSize(size);
setCurrentPage(1);
fetchData(
searchData.searchOption,
searchData.data,
searchData.logOption,
searchData.startDate && new Date(searchData.startDate).toISOString(),
searchData.endDate && new Date(searchData.endDate).toISOString(),
orderBy,
size,
1,
);
};
const fetchData = async (searchType, searchKey, historyType, startDt, endDt, order, size) => {
const token = sessionStorage.getItem('token');
setDataList(await LogViewList(token, searchType, searchKey, historyType, startDt, endDt, order ? order : orderBy, size ? size : pageSize, currentPage));
};
useEffect(() => {
fetchData(
searchData.searchOption,
searchData.data,
searchData.logOption,
searchData.startDate && new Date(searchData.startDate).toISOString(),
searchData.endDate && new Date(searchData.endDate).toISOString(),
orderBy,
pageSize,
);
}, [currentPage]);
const handleSearch = (searchType, searchKey, historyType, startDt, endDt) => {
fetchData(searchType, searchKey, historyType, startDt, endDt);
};
return (
<>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === 5) ? (
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={'view'}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={() => navigate(-1)} />
</BtnWrapper>
<ModalText $align="center">
해당 메뉴에 대한 조회 권한이 없습니다.
<br />
권한 등급을 변경 다시 이용해주세요.
</ModalText>
<BtnWrapper $gap="10px">
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={() => navigate(-1)} />
</BtnWrapper>
</Modal>
) : (
<>
<Title>사용 이력 조회</Title>
<FormWrapper action="" $flow="row">
<LogViewSearchBar handleSearch={handleSearch} resultData={setSearchData} />
</FormWrapper>
<TableInfo>
<ListCount>
: {dataList && dataList.total} / {dataList && dataList.total_all}
</ListCount>
<ListOption>
<SelectInput className="input-select" onChange={e => handleOrderBy(e)}>
<option value="DESC">최신일자</option>
<option value="ASC">오래된순</option>
</SelectInput>
<SelectInput className="input-select" onChange={e => handlePageSize(e)}>
<option value="50">50</option>
<option value="100">100</option>
</SelectInput>
</ListOption>
</TableInfo>
<TableStyle>
<caption></caption>
<thead>
<tr>
<th width="200">일시</th>
<th width="200">이름</th>
<th width="50%">ID</th>
<th width="30%">사용 이력</th>
{/* <th width="25%">상세 이력</th> */}
<th width="150">상세 보기</th>
</tr>
</thead>
<tbody>
{dataList.list &&
dataList.list.map((history, index) => (
<Fragment key={index}>
<tr>
<td>{convertKTC(history.create_dt)}</td>
<td>{history.name}</td>
<td>{history.mail}</td>
<td>{logOption.map(data => data.value === history.history_type && data.name)}</td>
<td>
<Button theme="line" text="JSON INFO" handleClick={() => handleButtonClick(history.content)} />
</td>
</tr>
</Fragment>
))}
</tbody>
</TableStyle>
<Pagination postsPerPage={pageSize} totalPosts={dataList && dataList.total_all} setCurrentPage={setCurrentPage} currentPage={currentPage} pageLimit={10} />
<Title>사용 이력 조회</Title>
{/* 조회조건 */}
<FormWrapper>
<CommonSearchBar
config={config}
searchParams={searchParams}
onSearch={(newParams, executeSearch = true) => {
if (executeSearch) {
handleSearch(newParams);
} else {
updateSearchParams(newParams);
}
}}
onReset={handleReset}
/>
</FormWrapper>
{/* 조회테이블 */}
<CaliTable
columns={tableInfo.columns}
data={displayData}
onAction={(action, item) => handleAction(action, item)}
// refProp={tableRef}
loading={loading}
/>
{/* 페이징 */}
{dataList?.list &&
<FrontPagination
data={dataList.list}
itemsPerPage={INITIAL_PAGE_SIZE}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
pageLimit={INITIAL_PAGE_LIMIT}
onPageChange={handleClientPageChange}
/>}
{/* 상세 */}
<LogDetailModal
viewMode="data"
detailView={modalState.detailModal}
handleDetailView={() => handleModalClose('detail')}
detailData={detailData}
title="상세정보"
/>
<LogViewModal stateModal={stateModal} handleModal={handleModal} data={detailData} />
</>
)}
</>
);
};
export default LogView;
export default withAuth(authType.adminLogSearchRead)(LogView);

View File

@@ -0,0 +1,170 @@
import { Fragment, useState, useEffect } from 'react';
import {LogViewSearchBar} from '../../components/ServiceManage';
import { Title, FormWrapper, SelectInput, TableInfo, ListCount, ListOption, TableStyle, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
import Button from '../../components/common/button/Button';
import Pagination from '../../components/common/Pagination/Pagination';
import Modal from '../../components/common/modal/Modal';
import LogViewModal from '../../components/UserManage/LogViewModal';
import { LogViewList } from '../../apis';
import { useNavigate } from 'react-router-dom';
import { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil';
import { logOption } from '../../assets/data';
import { convertKTC } from '../../utils';
const LogView = () => {
const navigate = useNavigate();
const userInfo = useRecoilValue(authList);
const [currentPage, setCurrentPage] = useState(1);
const [stateModal, setStateModal] = useState('hidden');
const [dataList, setDataList] = useState([]);
const [detailData, setDetailData] = useState('');
const [searchData, setSearchData] = useState({});
const [orderBy, setOrderBy] = useState('DESC');
const [pageSize, setPageSize] = useState('50');
const handleButtonClick = content => {
setStateModal('view');
setDetailData(content);
};
const handleModal = () => {
if (stateModal === 'hidden') {
setStateModal('view');
} else {
setStateModal('hidden');
}
};
const handleOrderBy = e => {
const order = e.target.value;
setOrderBy(order);
fetchData(
searchData.searchOption,
searchData.data,
searchData.logOption,
searchData.startDate && new Date(searchData.startDate).toISOString(),
searchData.endDate && new Date(searchData.endDate).toISOString(),
order,
pageSize,
);
};
const handlePageSize = e => {
const size = e.target.value;
setPageSize(size);
setCurrentPage(1);
fetchData(
searchData.searchOption,
searchData.data,
searchData.logOption,
searchData.startDate && new Date(searchData.startDate).toISOString(),
searchData.endDate && new Date(searchData.endDate).toISOString(),
orderBy,
size,
1,
);
};
const fetchData = async (searchType, searchKey, historyType, startDt, endDt, order, size) => {
const token = sessionStorage.getItem('token');
setDataList(await LogViewList(token, searchType, searchKey, historyType, startDt, endDt, order ? order : orderBy, size ? size : pageSize, currentPage));
};
useEffect(() => {
fetchData(
searchData.searchOption,
searchData.data,
searchData.logOption,
searchData.startDate && new Date(searchData.startDate).toISOString(),
searchData.endDate && new Date(searchData.endDate).toISOString(),
orderBy,
pageSize,
);
}, [currentPage]);
const handleSearch = (searchType, searchKey, historyType, startDt, endDt) => {
fetchData(searchType, searchKey, historyType, startDt, endDt);
};
return (
<>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === 5) ? (
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={'view'}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={() => navigate(-1)} />
</BtnWrapper>
<ModalText $align="center">
해당 메뉴에 대한 조회 권한이 없습니다.
<br />
권한 등급을 변경 다시 이용해주세요.
</ModalText>
<BtnWrapper $gap="10px">
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={() => navigate(-1)} />
</BtnWrapper>
</Modal>
) : (
<>
<Title>사용 이력 조회</Title>
<FormWrapper action="" $flow="row">
<LogViewSearchBar handleSearch={handleSearch} resultData={setSearchData} />
</FormWrapper>
<TableInfo>
<ListCount>
: {dataList && dataList.total} / {dataList && dataList.total_all}
</ListCount>
<ListOption>
<SelectInput className="input-select" onChange={e => handleOrderBy(e)}>
<option value="DESC">최신일자</option>
<option value="ASC">오래된순</option>
</SelectInput>
<SelectInput className="input-select" onChange={e => handlePageSize(e)}>
<option value="50">50</option>
<option value="100">100</option>
</SelectInput>
</ListOption>
</TableInfo>
<TableStyle>
<caption></caption>
<thead>
<tr>
<th width="200">일시</th>
<th width="200">이름</th>
<th width="50%">ID</th>
<th width="30%">사용 이력</th>
{/* <th width="25%">상세 이력</th> */}
<th width="150">상세 보기</th>
</tr>
</thead>
<tbody>
{dataList.list &&
dataList.list.map((history, index) => (
<Fragment key={index}>
<tr>
<td>{convertKTC(history.create_dt)}</td>
<td>{history.name}</td>
<td>{history.mail}</td>
<td>{logOption.map(data => data.value === history.history_type && data.name)}</td>
<td>
<Button theme="line" text="JSON INFO" handleClick={() => handleButtonClick(history.content)} />
</td>
</tr>
</Fragment>
))}
</tbody>
</TableStyle>
<Pagination postsPerPage={pageSize} totalPosts={dataList && dataList.total_all} setCurrentPage={setCurrentPage} currentPage={currentPage} pageLimit={10} />
<LogViewModal stateModal={stateModal} handleModal={handleModal} data={detailData} />
</>
)}
</>
);
};
export default LogView;

View File

@@ -344,6 +344,20 @@ export const TableWrapper = styled.div`
}
`;
export const PopupTableWrapper = styled.div`
min-width: 680px;
overflow: auto;
&::-webkit-scrollbar {
height: 10px;
}
&::-webkit-scrollbar-thumb {
background: #666666;
}
&::-webkit-scrollbar-track {
background: #d9d9d9;
}
`;
export const State = styled.span`
color: ${props => props.color || '#2c2c2c'};
`;

View File

@@ -735,6 +735,17 @@ export const TitleItemValue = styled.div`
color: ${props => props.color || 'black'};
`;
export const TitleItemLink = styled.a`
font-weight: ${props => props.fontWeight || 500};
color: ${props => props.color || 'black'};
text-decoration: none;
cursor: pointer;
&:hover {
text-decoration: underline;
}
`;
export const ChargeBtn = styled.button`
&&{
width: 60px;
@@ -831,4 +842,22 @@ export const CopyBtn = styled.div`
position: absolute;
right: 0;
top: 0;
`;
export const Tab = styled.div`
padding: 10px 20px;
cursor: pointer;
border-bottom: 2px solid ${props => props.$active ? '#4a6cf7' : 'transparent'};
color: ${props => props.$active ? '#4a6cf7' : '#666'};
font-weight: ${props => props.$active ? 'bold' : 'normal'};
transition: all 0.3s ease;
&:hover {
background-color: #f5f5f5;
}
`;
export const TabContent = styled.div`
display: ${props => props.$active ? 'block' : 'none'};
padding: 10px 0;
`;

View File

@@ -1,4 +1,5 @@
import * as optionsConfig from '../assets/data/options';
import { logFieldLabels } from '../assets/data/data';
export const convertKTC = (dt, nation = true) => {
if (!dt) return "";
@@ -73,4 +74,16 @@ export const loadConfig = async (configPath) => {
console.error(`Failed to load search configuration: ${configPath}`, error);
throw error;
}
};
export const getFieldLabel = (key, value) => {
if (logFieldLabels[key]) {
return logFieldLabels[key];
}
if (typeof value === 'string' && logFieldLabels[value]) {
return logFieldLabels[value];
}
return key;
};