Files
nice-app-web/src/pages/support/notice/list-page.tsx
focp212@naver.com 5c03f7f021 back 키 제어
2025-11-20 10:22:36 +09:00

211 lines
7.2 KiB
TypeScript

import { ChangeEvent, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { useNoticeListMutation } from '@/entities/support/api/use-notice-list-mutation';
import { DEFAULT_PAGE_PARAM } from '@/entities/common/model/constant';
import { DetailData, InformCl, NoticeItem, NoticeListParams, NoticeListResponse, SearchCl } from '@/entities/support/model/types';
import { SupportNoticeItem } from '@/entities/support/ui/notice-item';
import { DefaultRequestPagination, HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
import useIntersectionObserver from '@/widgets/intersection-observer';
import { NoticeDetail } from '@/entities/support/ui/detail/notice-detail';
import { useParams } from 'react-router';
import { showAlert } from '@/widgets/show-alert';
import { snackBar } from '@/shared/lib';
import { useDetailOnStore } from '@/shared/model/store';
export const NoticeListPage = () => {
const { navigate } = useNavigate();
const { seq } = useParams();
const { t } = useTranslation();
const [onActionIntersect, setOnActionIntersect] = useState<boolean>(false);
const [pageParam, setPageParam] = useState<DefaultRequestPagination>(DEFAULT_PAGE_PARAM);
const [informCl, setInformCl] = useState<InformCl | string>('');
const [searchKeyword, setSearchKeyword] = useState<string>('');
const [resultList, setResultList] = useState<Array<NoticeItem>>([]);
const { detailOn, setDetailOn } = useDetailOnStore();
const [detailSeq, setDetailSeq] = useState<number | string>();
useSetHeaderTitle(t('support.notice.title'));
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false);
useSetOnBack(() => {
navigate(PATHS.home);
});
const { mutateAsync: noticeList } = useNoticeListMutation();
const onIntersect: IntersectionObserverCallback = (entries: Array<IntersectionObserverEntry>) => {
entries.forEach((entry: IntersectionObserverEntry) => {
if(entry.isIntersecting){
if(onActionIntersect && !!pageParam.cursor){
setOnActionIntersect(false);
callList('page');
}
}
});
};
const { setTarget } = useIntersectionObserver({
threshold: 1,
onIntersect
});
const callList = (type?: string) => {
setOnActionIntersect(false);
let listParams: NoticeListParams = {
informCl: informCl,
searchCl: (!!searchKeyword)? SearchCl.HEAD: null,
searchKeyword: searchKeyword,
page: pageParam
};
if(type !== 'page' && listParams.page){
listParams.page.cursor = null;
}
noticeList(listParams).then((rs: NoticeListResponse) => {
if(type === 'page'){
setResultList([
...resultList,
...rs.content
]);
}
else{
setResultList(rs.content);
}
if(rs.hasNext
&& rs.nextCursor !== pageParam.cursor
&& rs.content.length === DEFAULT_PAGE_PARAM.size
){
setPageParam({
...pageParam,
...{ cursor: rs.nextCursor }
});
}
else{
setPageParam({
...pageParam,
...{ cursor: null }
});
}
setOnActionIntersect(
!!rs.hasNext
&& rs.nextCursor !== pageParam.cursor
&& rs.content.length === DEFAULT_PAGE_PARAM.size
);
}).catch((e: any) => {
if(e.response?.data?.error?.message){
snackBar(e.response?.data?.error?.message);
return;
}
});
};
const onClickToAction = () => {
// Remove focus from active element
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
callList();
};
const setDetailData = (detailData: DetailData) => {
setDetailOn(detailData.detailOn);
if(detailData?.seq){
setDetailSeq(detailData?.seq);
}
};
const getNoticeList = () => {
let rs = [];
for(let i=0;i<resultList.length;i++){
let noticeItem = resultList[i];
if(noticeItem){
rs.push(
<SupportNoticeItem
key={ `key-support-notice-item-${i}` }
noticeItem={ noticeItem }
setDetailData={ setDetailData }
></SupportNoticeItem>
);
}
}
return rs;
};
useEffect(() => {
callList();
}, [informCl]);
useEffect(() => {
if(seq){
setDetailOn(true);
setDetailSeq(parseInt(seq));
}
}, [seq]);
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<div className="notice114">
<div className="notice-controls">
<div className="notice-search">
<span
className="ic16 search"
aria-hidden="true"
onClick={ () => onClickToAction() }
></span>
<input
type="text"
placeholder={ t('support.notice.searchPlaceholder') }
value={ searchKeyword }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setSearchKeyword(e.target.value) }
onKeyDown={ (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && !e.nativeEvent.isComposing) {
onClickToAction();
}
}}
/>
</div>
<div className="notice-filter">
<select
className="flex-1"
value={ informCl }
onChange={ (e: ChangeEvent<HTMLSelectElement>) => setInformCl(e.target.value) }
>
<option value="">{ t('support.notice.categories.all') }</option>
<option value="INTEREST_FREE_INSTALLMENT">{ t('support.notice.categories.INTEREST_FREE_INSTALLMENT') }</option>
<option value="NEWS">{ t('support.notice.categories.NEWS') }</option>
<option value="SERVICE_DISRUPTION_NOTICE">{ t('support.notice.categories.SERVICE_DISRUPTION_NOTICE') }</option>
<option value="MAINTENANCE_NOTICE">{ t('support.notice.categories.MAINTENANCE_NOTICE') }</option>
<option value="EVENT">{ t('support.notice.categories.EVENT') }</option>
<option value="SERVICE_CHANGE_OR_ADDITION">{ t('support.notice.categories.SERVICE_CHANGE_OR_ADDITION') }</option>
<option value="IMPORTANT_NOTICE">{ t('support.notice.categories.IMPORTANT_NOTICE') }</option>
<option value="ADDITIONAL_SERVICE">{ t('support.notice.categories.ADDITIONAL_SERVICE') }</option>
</select>
</div>
</div>
<div className="notice-list-114">
{ getNoticeList() }
</div>
<div ref={ setTarget }></div>
</div>
</div>
</div>
<NoticeDetail
detailOn={ detailOn }
setDetailOn={ setDetailOn }
seq={ detailSeq }
></NoticeDetail>
</main>
</>
);
};