dynamodb pagination 추가

유저조회 우편 페이징 처리
hook 수정
This commit is contained in:
2025-04-03 15:48:55 +09:00
parent 9221a06a8e
commit f290f0dbf0
19 changed files with 266 additions and 88 deletions

View File

@@ -201,9 +201,9 @@ export const UserFriendListView = async (token, guid) => {
};
// 우편 조회
export const UserMailView = async (token, guid, option) => {
export const UserMailView = async (token, params) => {
try {
const res = await Axios.get(`/api/v1/users/mail?guid=${guid}&type=${option}`, {
const res = await Axios.post(`/api/v1/users/mail`, params, {
headers: { Authorization: `Bearer ${token}` },
});

View File

@@ -21,6 +21,11 @@ export {
languageType,
opLandCategoryType,
opLandOwnedType,
opSuccessType
opSuccessType,
opPickupType,
opReadType,
opYNType,
opUserSessionType,
opMailType,
} from './options'
export {benItems, MinuteList, HourList, caliumRequestInitData, STATUS_STYLES, months} from './data'

View File

@@ -267,6 +267,11 @@ export const opPickupType = [
{ value: false, name: '미수령' },
];
export const opMailType = [
{ value: 'RECEIVE', name: '받은 우편' },
{ value: 'SEND', name: '보낸 우편' },
];
// export const logAction = [
// { value: "None", name: "ALL" },
// { value: "AIChatDeleteCharacter", name: "NPC 삭제" },

View File

@@ -18,7 +18,7 @@ import { TableSkeleton } from '../Skeleton/TableSkeleton';
import { UserInfoSkeleton } from '../Skeleton/UserInfoSkeleton';
import { opUserSessionType } from '../../assets/data/options';
import Button from '../common/button/Button';
import { useModal } from '../../utils/hook';
import { useModal } from '../../hooks/hook';
import { InitData } from '../../apis/Data';
const UserDefaultInfo = ({ userInfo }) => {
@@ -38,6 +38,11 @@ const UserDefaultInfo = ({ userInfo }) => {
const [dataList, setDataList] = useState({});
const [adminLevel, setAdminLevel] = useState('0');
const [loading, setLoading] = useState(true);
const [authDelete, setAuthDelete] = useState(false);
useEffect(() => {
setAuthDelete(authInfo?.auth_list?.some(auth => auth.id === authType.userSearchDelete));
}, [authInfo]);
useEffect(() => {
fetchData();
@@ -126,8 +131,13 @@ const UserDefaultInfo = ({ userInfo }) => {
<tr>
<th>접속상태</th>
<StatusCell>{dataList.user_session !== undefined && opUserSessionType.find(session => session.value === dataList.user_session)?.name}
{<Button theme={dataList.user_session ? "line" : "disable"} id={"user_session"} name="kick" text="KICK" handleClick={e => handleSubmit('userKickSubmit')} disabled={!dataList.user_session}/>}
{/*{<Button theme={dataList.user_session ? "line" : "disable"} id={"user_session"} name="kick" text="KICK" handleClick={e => handleSubmit('userKickSubmit')} />}*/}
{<Button theme={(dataList.user_session && authDelete) ? "line" : "disable"}
id={"user_session"}
name="kick"
text="KICK"
handleClick={() => handleSubmit('userKickSubmit')}
disabled={!dataList.user_session && !authDelete}
/>}
</StatusCell>
</tr>
<tr>

View File

@@ -12,55 +12,67 @@ import CompletedModal from '../common/modal/CompletedModal';
import { useTranslation } from 'react-i18next';
import CustomConfirmModal from '../common/modal/CustomConfirmModal';
import DynamicModal from '../common/modal/DynamicModal';
import { authType, ivenTabType } from '../../assets/data';
import { authType, ivenTabType, opMailType } from '../../assets/data';
import { useRecoilValue } from 'recoil';
import { authList } from '../../store/authList';
import { convertKTC } from '../../utils';
import { TableSkeleton } from '../Skeleton/TableSkeleton';
import { opPickupType, opReadType, opYNType } from '../../assets/data/options';
import { eventSearchType, opPickupType, opReadType, opYNType } from '../../assets/data/options';
import { useDynamoDBPagination, useModal } from '../../hooks/hook';
import { DynamoPagination } from '../common';
const UserMailInfo = ({ userInfo }) => {
const token = sessionStorage.getItem('token');
const { t } = useTranslation();
const authInfo = useRecoilValue(authList);
//데이터 리스트
const [dataList, setDataList] = useState([]);
// 받은 우편, 보낸 우편
const [option, setOption] = useState('RECEIVE');
// 상세 정보
const [detail, setDetail] = useState({ title: '', content: '', item_list: [], mail_guid: '' });
const [deleteSelected, setDeleteSelected] = useState({});
const [itemUpdateCount, setItemUpdateCount] = useState('1');
const [authDelete, setAuthDelete] = useState(false);
const [loading, setLoading] = useState(true);
const [modalState, setModalState] = useState({
detailModal: 'hidden',
deleteItemModal: 'hidden',
deleteSubmitModal: 'hidden',
deleteCompleteModal: 'hidden',
deleteItemCompleteModal: 'hidden'
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
detail: 'hidden',
deleteItem: 'hidden',
deleteSubmit: 'hidden',
deleteComplete: 'hidden',
deleteItemComplete: 'hidden'
});
// 받은 우편, 보낸 우편 option 에 따른 data fetch
const fetchMailData = async (page, startKey) => {
const params = {
mail_type: option,
guid: userInfo.guid,
page_key: startKey
};
return await UserMailView(token, params);
};
const {
data: dataList,
loading,
pagination,
fetchPage,
goToNextPage,
goToPrevPage,
resetPagination
} = useDynamoDBPagination(fetchMailData);
useEffect(() => {
fetchData(option);
resetPagination();
fetchPage(1);
}, [option]);
useEffect(() => {
setAuthDelete(authInfo.auth_list.some(auth => auth.id === authType.userSearchDelete));
}, [authInfo]);
const fetchData = async option => {
setLoading(true);
await UserMailView(token, userInfo.guid, option).then(data =>{
setDataList(data);
setLoading(false);
});
};
// 상세 모달 value 세팅
const handleDetail = (title, content, itmeList, mail_guid) => {
setDetail({ title: title, content: content, item_list: itmeList, mail_guid: mail_guid });
};
@@ -81,20 +93,6 @@ const UserMailInfo = ({ userInfo }) => {
}
};
const handleModalView = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'view',
}));
}
const handleModalClose = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'hidden',
}));
}
const handleModalSubmit = async (type, param = null) => {
let params;
let result;
@@ -147,7 +145,7 @@ const UserMailInfo = ({ userInfo }) => {
// if(idx >= 0) {
// dataList.mail_list.splice(idx, 1);
// }
fetchData(option);
fetchPage(pagination.currentPage);
break;
case "deleteItemComplete":
handleModalClose('deleteItemComplete');
@@ -167,21 +165,28 @@ const UserMailInfo = ({ userInfo }) => {
return (
loading ? <TableSkeleton count={10}/> :
<>
<SelectWrapper>
<SelectContainer>
<SelectInput
value={option}
onChange={e => {
setOption(e.target.value);
}}>
<option value="RECEIVE">받은 우편</option>
<option value="SEND">보낸 우편</option>
{opMailType.map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput>
</SelectWrapper>
<DynamoPagination
pagination={pagination}
onNextPage={goToNextPage}
onPrevPage={goToPrevPage}
/>
</SelectContainer>
{option === 'RECEIVE' && (
<>
{/* <CheckWrapper>
<CheckBox id="viewDelReceiveMail" label="삭제 우편 보기" />
</CheckWrapper> */}
<UserTableWrapper>
<UserDefaultTable>
<thead>
@@ -197,7 +202,7 @@ const UserMailInfo = ({ userInfo }) => {
</tr>
</thead>
<tbody>
{dataList.mail_list &&
{dataList && dataList.mail_list &&
dataList.mail_list.map((mail, idx) => {
return (
<tr key={idx}>
@@ -230,9 +235,6 @@ const UserMailInfo = ({ userInfo }) => {
)}
{option === 'SEND' && (
<>
{/* <CheckWrapper>
<CheckBox id="viewDelSendMail" label="삭제 우편 보기" />
</CheckWrapper> */}
<UserTableWrapper>
<UserDefaultTable>
<thead>
@@ -244,7 +246,7 @@ const UserMailInfo = ({ userInfo }) => {
</tr>
</thead>
<tbody>
{dataList.mail_list &&
{dataList && dataList.mail_list &&
dataList.mail_list.map((mail, idx) => {
return (
<tr key={idx}>
@@ -269,6 +271,8 @@ const UserMailInfo = ({ userInfo }) => {
</UserTableWrapper>
</>
)}
{/*상세*/}
<MailDetailModal
mailModal={modalState.detailModal}
@@ -370,22 +374,6 @@ const MailLink = styled.div`
cursor: pointer;
`;
const SelectWrapper = styled.div`
select {
height: 30px;
}
margin-bottom: 10px;
`;
const CheckWrapper = styled.div`
text-align: right;
padding: 10px;
margin-top: -40px;
height: 30px;
margin-bottom: 10px;
font-size: 14px;
font-weight: 700;
`;
const InputItem = styled.div`
display: flex;
flex-direction: column;
@@ -398,4 +386,42 @@ const InputItem = styled.div`
width: 100px;
padding: 10px 20px;
}
`;
const SelectContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
select {
height: 30px;
}
`;
const PaginationButtons = styled.div`
display: flex;
align-items: center;
gap: 10px;
`;
const PaginationButton = styled.button`
background-color: ${props => props.disabled ? '#e0e0e0' : '#6c7eb7'};
color: ${props => props.disabled ? '#a0a0a0' : 'white'};
border: none;
padding: 6px 12px;
border-radius: 4px;
font-size: 12px;
cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
transition: background-color 0.2s;
&:hover {
background-color: ${props => props.disabled ? '#e0e0e0' : '#5a6a9b'};
}
`;
const PageInfo = styled.div`
font-size: 12px;
font-weight: 500;
color: #666;
`;

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ import {
import { modalTypes } from '../../../assets/data';
import { DynamicModal, Modal, SingleDatePicker, SingleTimePicker } from '../../common';
import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../../assets/data/adminConstants';
import { useModal } from '../../../utils/hook';
import { useModal } from '../../../hooks/hook';
import { convertKTCDate } from '../../../utils';
import { BattleEventModify, BattleEventSingleRegist } from '../../../apis/Battle';
import { battleEventStatusType } from '../../../assets/data/types';
@@ -144,7 +144,6 @@ const OwnerChangeModal = ({ modalType, detailView, handleDetailView, content, se
}
setLoading(true);
await UserInfoView(token, guid).then(data => {
setLoading(false);
if(Object.keys(data).length === 0){
setAlertMsg(t('WARNING_GUID_CHECK'));
setResultData({ ...resultData, user_name: '' })
@@ -154,13 +153,14 @@ const OwnerChangeModal = ({ modalType, detailView, handleDetailView, content, se
setResultData({ ...resultData, user_name: nickname })
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
}).finally(()=>{
setLoading(false);
});
break;
case "registConfirm":
setLoading(true);
if(isView()){
console.log(resultData);
setLoading(false);
handleModalClose('registConfirm');
@@ -173,7 +173,6 @@ const OwnerChangeModal = ({ modalType, detailView, handleDetailView, content, se
}
await LandOwnerChangesDelete(token, resultData).then(data => {
setLoading(false);
handleModalClose('registConfirm');
if(data.result === "SUCCESS") {
handleModalView('registComplete');
@@ -184,10 +183,11 @@ const OwnerChangeModal = ({ modalType, detailView, handleDetailView, content, se
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
}).finally(() => {
setLoading(false);
});
}else{
await LandOwnedChangesRegist(token, resultData).then(data => {
setLoading(false);
handleModalClose('registConfirm');
if(data.result === "SUCCESS") {
handleModalView('registComplete');
@@ -202,6 +202,8 @@ const OwnerChangeModal = ({ modalType, detailView, handleDetailView, content, se
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
}).finally(() => {
setLoading(false);
});
}

View File

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

View File

@@ -11,6 +11,7 @@ import DynamicModal from './modal/DynamicModal';
import CustomConfirmModal from './modal/CustomConfirmModal';
import Modal from './modal/Modal';
import Pagination from './Pagination/Pagination';
import DynamoPagination from './Pagination/DynamoPagination';
import ViewTableInfo from './Table/ViewTableInfo';
import Loading from './Loading';
import CDivider from './CDivider';
@@ -40,5 +41,6 @@ export { DateTimeInput,
ViewTableInfo,
Loading,
CDivider,
TopButton
TopButton,
DynamoPagination
};

View File

@@ -188,4 +188,77 @@ export const useDataFetch = (fetchFunction, dependencies = [], initialState = nu
refetch: fetchData,
setData
};
};
export const useDynamoDBPagination = (fetchFunction) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({
currentPage: 1,
pageKeys: { 1: null },
hasNextPage: false
});
const resetPagination = useCallback(() => {
setPagination({
currentPage: 1,
pageKeys: { 1: null },
hasNextPage: false
});
}, []);
const fetchPage = useCallback(async (page) => {
setLoading(true);
try {
const startKey = pagination.pageKeys[page];
const response = await fetchFunction(page, startKey);
setData(response);
const updatedPagination = { ...pagination, currentPage: page };
if (response.pageKey) {
updatedPagination.pageKeys[page + 1] = response.pageKey;
updatedPagination.hasNextPage = true;
} else {
updatedPagination.hasNextPage = false;
}
setPagination(updatedPagination);
return response;
} catch (error) {
console.error('페이지 데이터 가져오기 오류:', error);
throw error;
} finally {
setLoading(false);
}
}, [fetchFunction, pagination]);
const goToNextPage = useCallback(() => {
if (!pagination.hasNextPage) return;
const nextPage = pagination.currentPage + 1;
return fetchPage(nextPage);
}, [pagination, fetchPage]);
const goToPrevPage = useCallback(() => {
if (pagination.currentPage <= 1) return;
const prevPage = pagination.currentPage - 1;
return fetchPage(prevPage);
}, [pagination, fetchPage]);
return {
data,
loading,
pagination,
setData,
setPagination,
fetchPage,
goToNextPage,
goToPrevPage,
resetPagination
};
};

View File

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

View File

@@ -13,7 +13,7 @@ import {
DetailTableInfo,
} from '../../styles/Components';
import { withAuth } from '../../utils/hook';
import { withAuth } from '../../hooks/hook';
import {
authType,
modalTypes,

View File

@@ -14,7 +14,7 @@ import Button from '../../components/common/button/Button';
import { useNavigate } from 'react-router-dom';
import { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil';
import { useDataFetch, useModal, useTable, withAuth } from '../../utils/hook';
import { useDataFetch, useModal, useTable, withAuth } from '../../hooks/hook';
import {
authType,
landSize,

View File

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

View File

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

View File

@@ -24,7 +24,7 @@ import { useTranslation } from 'react-i18next';
import { MailReceiver, RegistInputRow } from '../../styles/ModuleComponents';
import AuthModal from '../../components/common/modal/AuthModal';
import { authType } from '../../assets/data';
import { useDataFetch } from '../../utils/hook';
import { useDataFetch } from '../../hooks/hook';
import { BattleConfigView } from '../../apis/Battle';
import { currencyCodeTypes } from '../../assets/data/types';
import { DynamicModal } from '../../components/common';

View File

@@ -21,7 +21,7 @@ import {Button, ExcelDownButton, Pagination, DynamicModal, ViewTableInfo, Loadin
import { convertKTC, truncateText } from '../../utils';
import { CaliumRequestRegistModal, CaliumRequestSearchBar } from '../../components/UserManage';
import { CaliumCharge, CaliumRequestView } from '../../apis';
import { withAuth } from '../../utils/hook';
import { withAuth } from '../../hooks/hook';
import { convertEndDateToISO, convertStartDateToISO } from '../../utils/date';
const CaliumRequest = () => {

View File

@@ -23,7 +23,7 @@ import {
FormRowGroup, MessageWrapper,
} from '../../styles/ModuleComponents';
import { authType } from '../../assets/data';
import { useModal, withAuth } from '../../utils/hook';
import { useModal, withAuth } from '../../hooks/hook';
import { DynamicModal, TopButton } from '../../components/common';
import { opInitDataType, opSuccessType } from '../../assets/data/options';
import { InitData } from '../../apis/Data';