Refactor additional service filters and update related pages

- Move filter components to separate ars/filter and payout directories
- Update additional service types and list item component
- Modify page routes and path constants

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
HyeonJongKim
2025-11-04 11:04:25 +09:00
parent 872a3f74db
commit 75d5837707
11 changed files with 351 additions and 45 deletions

View File

@@ -1,5 +1,5 @@
import { DefaulResponsePagination, DefaultRequestPagination } from '@/entities/common/model/types';
import { ListItemProps } from '../types';
import { AdditionalServiceCategory, DetailData, ListItemProps } from '../types';
export enum PayoutSearchDateType {
@@ -13,6 +13,14 @@ export enum PayoutDisbursementStatus {
FAIL = 'FAIL',
};
export interface PayoutListProps {
additionalServiceCategory: AdditionalServiceCategory;
listItems: Array<ListItemProps>;
searchDateType: PayoutSearchDateType
mid: string;
setDetailData: (detailData: DetailData) => void;
}
export interface PayoutListItem {
tid?: string;
submallId?: string;
@@ -31,7 +39,7 @@ export interface ExtensionPayoutRequestParams {
};
export interface ExtensionPayoutRequestResponse {
status: boolean;
error?: {
message?: string
}

View File

@@ -13,8 +13,8 @@ import {
} from '@/entities/common/model/constant';
import moment from 'moment';
import { FilterInput } from '@/shared/ui/filter/input';
import { OrderStatus, PaymentStatus } from '../../model/ars/types';
import { getArsOrderStatusBtnGroup, getArsPaymentStatusBtnGroup } from '../../model/ars/constant';
import { OrderStatus, PaymentStatus } from '../../../model/ars/types';
import { getArsOrderStatusBtnGroup, getArsPaymentStatusBtnGroup } from '../../../model/ars/constant';
import { useStore } from '@/shared/model/store';
import { FilterSelectMid } from '@/shared/ui/filter/select-mid';
import { FullMenuClose } from '@/entities/common/ui/full-menu-close';

View File

@@ -161,7 +161,7 @@ export const ListItem = ({
}
//이하 상세페이지 존재
else if (additionalServiceCategory === AdditionalServiceCategory.AccountHolderAuth) {
if(setDetailData && !!mid && !!tid){
if (setDetailData && !!mid && !!tid) {
setDetailData({
mid: mid,
tid: tid,
@@ -220,13 +220,13 @@ export const ListItem = ({
}
else if (additionalServiceCategory === AdditionalServiceCategory.Payout) {
navigate(PATHS.additionalService.payout.detail, {
state: {
additionalServiceCategory: additionalServiceCategory,
if (setDetailData && !!mid && !!tid) {
setDetailData({
mid: mid,
tid: tid
}
});
tid: tid,
detailOn: true
});
}
}
else if (additionalServiceCategory === AdditionalServiceCategory.Ars) {
if (setDetailData && !!mid && !!tid) {

View File

@@ -0,0 +1,208 @@
import moment from 'moment';
import { motion } from 'framer-motion';
import { ExtensionPayoutDetailDownloadCertificateParams, ExtensionPayoutDetailDownloadCertificateResponse, ExtensionPayoutDetailParams, ExtensionPayoutDetailResponse } from "@/entities/additional-service/model/payout/types";
import { useTranslation } from "react-i18next";
import { useEffect, useState } from 'react';
import { useExtensionPayoutDetailMutation } from '@/entities/additional-service/api/payout/use-extension-payout-detail-mutation';
import { useExtensionPayoutDetailDownloadCertificateMutation } from '@/entities/additional-service/api/payout/use-extension-payout-detail-download-cetificate-mutation';
import { DetailMotionDuration, DetailMotionStyle, DetailMotionVariants } from '@/entities/common/model/constant';
import { FullMenuClose } from '@/entities/common/ui/full-menu-close';
import { DownloadTypeBottomSheet } from '@/entities/common/ui/download-type-bottom-sheet';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
export interface PayoutDetailProps {
detailOn: boolean;
setDetailOn: (detailOn: boolean) => void;
mid: string;
tid: string;
};
export const PayoutDetail = ({
detailOn,
setDetailOn,
mid,
tid
}: PayoutDetailProps) => {
const { t } = useTranslation();
const [detail, setDetail] = useState<ExtensionPayoutDetailResponse>();
const [downloadTypeBottomSheetOn, setDownloadTypeBottomSheetOn] = useState<boolean>(false);
const [emailBottomSheetOn, setEmailBottomSheetOn] = useState<boolean>(false);
const { mutateAsync: extensionPayoutDetail } = useExtensionPayoutDetailMutation();
const { mutateAsync: extensionPayoutDetailDownloadCertification } = useExtensionPayoutDetailDownloadCertificateMutation();
const callDetail = () => {
let params: ExtensionPayoutDetailParams = {
tid: tid,
mid: mid,
};
extensionPayoutDetail(params).then((rs: ExtensionPayoutDetailResponse) => {
setDetail(rs);
});
}
const onClickToDownload = () => {
setDownloadTypeBottomSheetOn(true);
};
const onSelectDownloadType = (type: 'IMAGE' | 'EMAIL') => {
if (type === 'IMAGE') {
// Save image directly
const params: ExtensionPayoutDetailDownloadCertificateParams = {
mid: mid,
tid: tid,
requestType: 'IMAGE',
email: ''
};
extensionPayoutDetailDownloadCertification(params)
.then((rs: ExtensionPayoutDetailDownloadCertificateResponse) => {
console.log('Certificate Download Status:', rs);
})
.catch((error) => {
console.error('Certificate Download Failed:', error);
});
} else {
// Open EmailBottomSheet for email option
setEmailBottomSheetOn(true);
}
};
const onSendRequest = (selectedEmail?: string) => {
if (selectedEmail) {
const params: ExtensionPayoutDetailDownloadCertificateParams = {
mid: mid,
tid: tid,
requestType: 'EMAIL',
email: selectedEmail
};
extensionPayoutDetailDownloadCertification(params)
.then((rs: ExtensionPayoutDetailDownloadCertificateResponse) => {
console.log('Certificate Download Status:', rs);
})
.catch((error) => {
console.error('Certificate Download Failed:', error);
});
}
setEmailBottomSheetOn(false);
};
const onClickToClose = () => {
setDetailOn(false);
};
useEffect(() => {
if (!!mid && !!tid) {
callDetail();
}
}, [mid, tid]);
return (
<>
<motion.div
className="full-menu-modal"
initial="hidden"
animate={(detailOn) ? 'visible' : 'hidden'}
variants={DetailMotionVariants}
transition={DetailMotionDuration}
style={DetailMotionStyle}
>
<div className="full-menu-container pdw-16">
<div className="full-menu-header">
<div className="full-menu-title center">{t('additionalService.payout.detailTitle')}</div>
<div className="full-menu-actions">
<FullMenuClose
addClass="full-menu-close"
onClickToCallback={onClickToClose}
></FullMenuClose>
</div>
</div>
<div className="tab-pane sub active">
<div className="pay-top">
<div className="num-amount">
<span className="amount">
{t('home.money', { value: new Intl.NumberFormat('en-US').format(detail?.disbursementAmount || 0) })}
</span>
</div>
<div className="num-store">{detail?.companyName}</div>
<div className="num-day">{detail?.settlementDate}</div>
<div className="receipt-row">
<button
className="receipt-btn"
type="button"
onClick={onClickToDownload}
>
<span className="icon-24 download"></span>
<span>{t('additionalService.payout.depositCertificate')}</span>
</button>
</div>
</div>
<div className="detail-divider"></div>
<div className="pay-detail">
<div className="detail-title">{t('additionalService.payout.detailInfo')}</div>
<ul className="kv-list">
<li className="kv-row">
<span className="k">{t('additionalService.payout.disbursementStatus')}</span>
<span className="v">{detail?.disbursementStatus}</span>
</li>
<li className="kv-row">
<span className="k">{t('additionalService.payout.transactionType')}</span>
<span className="v">{detail?.transTypeName}</span>
</li>
<li className="kv-row">
<span className="k">{t('common.requestDate')}</span>
<span className="v">{moment(detail?.requestDate).format('YYYY.MM.DD')}</span>
</li>
<li className="kv-row">
<span className="k">{t('additionalService.payout.disbursementDateTime')}</span>
<span className="v">{moment(detail?.settlementDateTime, 'YYYYMMDDHHmmss').format('YYYY.MM.DD HH:mm:ss')}</span>
</li>
<li className="kv-row">
<span className="k">{t('additionalService.payout.businessNumber')}</span>
<span className="v">{detail?.companyNo}</span>
</li>
<li className="kv-row">
<span className="k">{t('additionalService.payout.accountHolder')}</span>
<span className="v">{detail?.accountName}</span>
</li>
<li className="kv-row">
<span className="k">{t('additionalService.payout.bank')}</span>
<span className="v">{detail?.bankName}</span>
</li>
<li className="kv-row">
<span className="k">{t('additionalService.payout.accountNumber')}</span>
<span className="v">{detail?.accountNo}</span>
</li>
<li className="kv-row">
<span className="k">{t('additionalService.payout.depositor')}</span>
<span className="v">{detail?.depositName}</span>
</li>
<li className="kv-row">
<span className="k">{t('additionalService.payout.failureReason')}</span>
<span className="v">{detail?.failReason}</span>
</li>
</ul>
</div>
</div>
</div>
<DownloadTypeBottomSheet
bottomSheetOn={downloadTypeBottomSheetOn}
setBottomSheetOn={setDownloadTypeBottomSheetOn}
onSelectType={onSelectDownloadType}
/>
<EmailBottomSheet
bottomSheetOn={emailBottomSheetOn}
setBottomSheetOn={setEmailBottomSheetOn}
imageSave={false}
sendEmail={true}
sendRequest={onSendRequest}
/>
</motion.div>
</>
)
}

View File

@@ -8,11 +8,11 @@ import { FilterRangeAmount } from '@/shared/ui/filter/range-amount';
import {
PayoutDisbursementStatus,
PayoutSearchDateType
} from '../../model/payout/types';
} from '../../../model/payout/types';
import {
getPayoutSearchClBtnGroup,
getPayoutDisbursementStatusBtnGroup
} from '../../model/payout/constant';
} from '../../../model/payout/constant';
import {
FilterMotionDuration,
FilterMotionStyle,

View File

@@ -0,0 +1,69 @@
import { PayoutListProps, PayoutSearchDateType } from "../../model/payout/types";
import { AdditionalServiceCategory } from "../../model/types";
import { ListDateGroup } from "../list-date-group";
export const PayoutList = ({
additionalServiceCategory,
listItems,
searchDateType,
mid,
setDetailData
}: PayoutListProps) => {
const getListDateGroup = () => {
let rs = [];
let date = '';
let list = [];
for (let i = 0; i < listItems.length; i++) {
let itemDateStr = '';
if (searchDateType === PayoutSearchDateType.REQUEST_DATE) {
itemDateStr = listItems[i]?.requestDate || '';
} else if (searchDateType === PayoutSearchDateType.SETTLEMENT_DATE) {
itemDateStr = listItems[i]?.settlementDate || '';
}
let itemDate = itemDateStr.substring(0, 8);
if (i === 0) {
date = itemDate;
}
if (date !== itemDate) {
if (list.length > 0) {
rs.push(
<ListDateGroup
additionalServiceCategory={additionalServiceCategory}
mid={mid}
key={date + '-' + i}
date={date}
items={list}
setDetailData={setDetailData}
></ListDateGroup>
);
}
date = itemDate;
list = [];
}
list.push(listItems[i] as any);
}
if (list.length > 0) {
rs.push(
<ListDateGroup
additionalServiceCategory={additionalServiceCategory}
mid={mid}
key={date + '-last'}
date={date}
items={list}
setDetailData={setDetailData}
></ListDateGroup>
);
}
return rs;
};
return (
<>
<section className="transaction-list">
{getListDateGroup()}
</section>
</>
)
}

View File

@@ -25,7 +25,6 @@ import { SettlementAgencyMemberPage } from './settlement-agency/member-page';
import { SettlementAgencyRegisterPage } from './settlement-agency/register-page';
import { SettlementAgencyDetailPage } from './settlement-agency/detail-page';
import { PayoutListPage } from './payout/list-page';
import { PayoutDetailPage } from './payout/detail-page';
import { PayoutRequestPage } from './payout/request-page';
import { LinkPaymentApplyPage } from './link-payment/apply/link-payment-apply-page';
import { LinkPaymentApplyConfirmPage } from './link-payment/apply/link-payment-apply-confirm-page';
@@ -90,7 +89,6 @@ export const AdditionalServicePages = () => {
</Route>
<Route path={ROUTE_NAMES.additionalService.payout.base}>
<Route path={ROUTE_NAMES.additionalService.payout.list} element={<PayoutListPage />} />
<Route path={ROUTE_NAMES.additionalService.payout.detail} element={<PayoutDetailPage />} />
<Route path={ROUTE_NAMES.additionalService.payout.request} element={<PayoutRequestPage />} />
</Route>
<Route path={ROUTE_NAMES.additionalService.faceAuth.base}>

View File

@@ -18,7 +18,7 @@ import { ListDateGroup } from '@/entities/additional-service/ui/list-date-group'
import { AdditionalServiceCategory, DetailData } from '@/entities/additional-service/model/types';
import { SortTypeBox } from '@/entities/common/ui/sort-type-box';
import { getArsPaymentStatusBtnGroup } from '@/entities/additional-service/model/ars/constant';
import { ArsFilter } from '@/entities/additional-service/ui/filter/ars-filter';
import { ArsFilter } from '@/entities/additional-service/ui/ars/filter/ars-filter';
import { useTranslation } from 'react-i18next';
import { useStore } from '@/shared/model/store';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';

View File

@@ -23,15 +23,17 @@ import {
import moment from 'moment';
import { SortTypeBox } from '@/entities/common/ui/sort-type-box';
import { useExtensionPayoutExcelMutation } from '@/entities/additional-service/api/payout/use-extension-payout-excel-mutation';
import { PayoutFilter } from '@/entities/additional-service/ui/filter/payout-filter';
import { PayoutFilter } from '@/entities/additional-service/ui/payout/filter/payout-filter';
import { getPayoutDisbursementStatusBtnGroup } from '@/entities/additional-service/model/payout/constant';
import { ListDateGroup } from '@/entities/additional-service/ui/list-date-group';
import { useTranslation } from 'react-i18next';
import { AdditionalServiceCategory } from '@/entities/additional-service/model/types';
import { AdditionalServiceCategory, DetailData } from '@/entities/additional-service/model/types';
import { useStore } from '@/shared/model/store';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
import useIntersectionObserver from '@/widgets/intersection-observer';
import { PayoutList } from '@/entities/additional-service/ui/payout/payout-list';
import { PayoutDetail } from '@/entities/additional-service/ui/payout/detail/payout-detail';
export const PayoutListPage = () => {
// Access check
@@ -56,22 +58,26 @@ export const PayoutListPage = () => {
const [maxAmount, setMaxAmount] = useState<number | undefined>();
const [emailBottomSheetOn, setEmailBottomSheetOn] = useState<boolean>(false);
const [detailOn, setDetailOn] = useState<boolean>(false);
const [detailMid, setDetailMid] = useState<string>('');
const [detailTid, setDetailTid] = useState<string>('');
const { mutateAsync: extensionPayoutList } = useExtensionPayoutListMutation();
const { mutateAsync: extensionPayoutExcel } = useExtensionPayoutExcelMutation();
const onIntersect: IntersectionObserverCallback = (entries: Array<IntersectionObserverEntry>) => {
entries.forEach((entry: IntersectionObserverEntry) => {
if(entry.isIntersecting){
if(onActionIntersect && !!pageParam.cursor){
if (entry.isIntersecting) {
if (onActionIntersect && !!pageParam.cursor) {
setOnActionIntersect(false);
callList('page');
}
}
}
});
};
const { setTarget } = useIntersectionObserver({
threshold: 1,
onIntersect
const { setTarget } = useIntersectionObserver({
threshold: 1,
onIntersect
});
useSetHeaderTitle(t('additionalService.payout.title'));
@@ -96,41 +102,41 @@ export const PayoutListPage = () => {
minAmount: minAmount,
maxAmount: maxAmount,
page: {
...pageParam,
...{ sortType: sortType }
}
...pageParam,
...{ sortType: sortType }
}
};
if(type !== 'page' && listParams.page){
if (type !== 'page' && listParams.page) {
listParams.page.cursor = null;
}
extensionPayoutList(listParams).then((rs: ExtensionPayoutListResponse) => {
if(type === 'page'){
if (type === 'page') {
setListItems([
...listItems,
...rs.content
]);
}
else{
else {
setListItems(rs.content);
}
if(rs.hasNext
if (rs.hasNext
&& rs.nextCursor !== pageParam.cursor
&& rs.content.length === DEFAULT_PAGE_PARAM.size
){
) {
setPageParam({
...pageParam,
...{ cursor: rs.nextCursor }
});
}
else{
else {
setPageParam({
...pageParam,
...{ cursor: null }
});
}
setOnActionIntersect(
!!rs.hasNext
!!rs.hasNext
&& rs.nextCursor !== pageParam.cursor
&& rs.content.length === DEFAULT_PAGE_PARAM.size
);
@@ -155,6 +161,17 @@ export const PayoutListPage = () => {
}
setEmailBottomSheetOn(false);
};
const setDetailData = (detailData: DetailData) => {
setDetailOn(detailData.detailOn);
if (detailData.mid) {
setDetailMid(detailData.mid);
}
if (detailData.tid) {
setDetailTid(detailData.tid);
}
};
const onClickToOpenFilter = () => {
setFilterOn(!filterOn);
};
@@ -276,7 +293,7 @@ export const PayoutListPage = () => {
</div>
</section>
<section className="filter-section">
<div className="filter-section">
<SortTypeBox
sortType={sortType}
onClickToSort={onClickToSort}
@@ -294,11 +311,15 @@ export const PayoutListPage = () => {
}
</div>
</div>
</section>
<section className="transaction-list">
{ getListDateGroup() }
</section>
<div ref={ setTarget }></div>
</div>
<PayoutList
additionalServiceCategory={AdditionalServiceCategory.Payout}
listItems={listItems}
searchDateType={searchDateType}
mid={mid}
setDetailData={setDetailData}
></PayoutList>
<div ref={setTarget}></div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
@@ -326,6 +347,13 @@ export const PayoutListPage = () => {
setMinAmount={setMinAmount}
setMaxAmount={setMaxAmount}
></PayoutFilter>
<PayoutDetail
detailOn={detailOn}
setDetailOn={setDetailOn}
mid={detailMid}
tid={detailTid}
>
</PayoutDetail>
<EmailBottomSheet
bottomSheetOn={emailBottomSheetOn}
setBottomSheetOn={setEmailBottomSheetOn}

View File

@@ -266,10 +266,6 @@ export const PATHS: RouteNamesType = {
`${ROUTE_NAMES.additionalService.base}${ROUTE_NAMES.additionalService.payout.base}`,
ROUTE_NAMES.additionalService.payout.list,
),
detail: generatePath(
`${ROUTE_NAMES.additionalService.base}${ROUTE_NAMES.additionalService.payout.base}`,
ROUTE_NAMES.additionalService.payout.detail,
),
request: generatePath(
`${ROUTE_NAMES.additionalService.base}${ROUTE_NAMES.additionalService.payout.base}`,
ROUTE_NAMES.additionalService.payout.request,

View File

@@ -119,7 +119,6 @@ export const ROUTE_NAMES = {
payout: {
base: '/payout/*',
list: 'list',
detail: 'detail',
request: 'request',
},
faceAuth: {