This commit is contained in:
focp212@naver.com
2025-10-25 21:48:27 +09:00
33 changed files with 657 additions and 284 deletions

View File

@@ -0,0 +1,31 @@
import axios from 'axios';
import { API_URL_ADDITIONAL_SERVICE } from '@/shared/api/api-url-additional-service';
import { resultify } from '@/shared/lib/resultify';
import { CBDCAxiosError } from '@/shared/@types/error';
import {
ExtensionCheckParams,
ExtensionCheckResponse,
ExtensionListParams,
ExtensionListResponse
} from '../model/types';
import {
useMutation,
UseMutationOptions
} from '@tanstack/react-query';
export const extensionCheck = (params: ExtensionCheckParams) => {
return resultify(
axios.post<ExtensionCheckResponse>(API_URL_ADDITIONAL_SERVICE.extensionCheck(), params),
);
};
export const useExtensionCheckMutation = (options?: UseMutationOptions<ExtensionCheckResponse, CBDCAxiosError, ExtensionCheckParams>) => {
const mutation = useMutation<ExtensionCheckResponse, CBDCAxiosError, ExtensionCheckParams>({
...options,
mutationFn: (params: ExtensionCheckParams) => extensionCheck(params),
});
return {
...mutation,
};
};

View File

@@ -81,10 +81,10 @@ export const getAlimtalkServiceCodeText = (status?: string): string => {
if (!status) return ''; if (!status) return '';
const serviceCodeMap: Record<string, string> = { const serviceCodeMap: Record<string, string> = {
'01': '카드', 'CARD': '신용카드',
'02': '신용카드', 'BANK': '계좌이체',
'03': '가상계좌', 'VBANK': '가상계좌',
'05': '휴대폰' 'PHONE': '휴대폰'
} }
return serviceCodeMap[status] || status; return serviceCodeMap[status] || status;

View File

@@ -126,14 +126,14 @@ export interface ExtensionAlimtalkDetailParams {
export interface ExtensionAlimtalkDetailResponse { export interface ExtensionAlimtalkDetailResponse {
receiverName: string; receiverName: string;
merchantName: string; companyName: string;
sendDate: string; sendDate: string;
mid: string; mid: string;
tid: string; tid: string;
extensionServiceName: string; extensionServiceName: string;
sendType: AlimtalkSendType; sendType: AlimtalkSendType;
senderName: string; senderName: string;
paymentMethod: string; serviceCode: string;
alimCl: AlimtalkAlimCl; alimCl: AlimtalkAlimCl;
sendCl: AlimTalkSendCl; sendCl: AlimTalkSendCl;
}; };

View File

@@ -9,4 +9,16 @@ export const PayoutDisbursementStatusBtnGroup = [
{name: '요청', value: PayoutDisbursementStatus.REQUEST}, {name: '요청', value: PayoutDisbursementStatus.REQUEST},
{name: '성공', value: PayoutDisbursementStatus.SUCCESS}, {name: '성공', value: PayoutDisbursementStatus.SUCCESS},
{name: '실패', value: PayoutDisbursementStatus.FAIL}, {name: '실패', value: PayoutDisbursementStatus.FAIL},
]; ];
export const getPayoutStatusText = (status?: string): string => {
if (!status) return '';
const statusMap: Record<string, string> = {
"REQUEST" : "요청",
"SUCCESS" : "성공",
"FAIL" : "실패"
}
return statusMap[status] || status;
}

View File

@@ -6,7 +6,9 @@ export interface ExtensionPayoutRequestParams {
disbursementAmount: number; disbursementAmount: number;
settlementDate: string; settlementDate: string;
}; };
export interface ExtensionPayoutRequestResponse {}; export interface ExtensionPayoutRequestResponse {
status: boolean;
};
export enum PayoutSearchDateType { export enum PayoutSearchDateType {
REQUEST_DATE = 'REQUEST_DATE', REQUEST_DATE = 'REQUEST_DATE',
SETTLEMENT_DATE = 'SETTLEMENT_DATE', SETTLEMENT_DATE = 'SETTLEMENT_DATE',
@@ -36,10 +38,15 @@ export interface PayoutContent {
requestDate?: string; requestDate?: string;
settlementDate?: string; settlementDate?: string;
companyName?: string; companyName?: string;
disbursementStatus?: PayoutDisbursementStatus; status?: PayoutDisbursementStatus;
disbursementAmount?: number; amount?: number;
};
export interface ExtensionPayoutExcelParams {
mid: string;
email: string;
fromDate: string;
toDate: string;
}; };
export interface ExtensionPayoutExcelParams extends ExtensionPayoutListParams {};
export interface ExtensionPayoutExcelResponse {} export interface ExtensionPayoutExcelResponse {}

View File

@@ -117,6 +117,7 @@ export interface ExtensionSmsDownloadExcelParams extends ExtensionRequestParams
fromDate: string; fromDate: string;
toDate: string; toDate: string;
smsCl: string; smsCl: string;
} }
export interface ExtensionSmsDownloadExcelResponse { export interface ExtensionSmsDownloadExcelResponse {

View File

@@ -10,6 +10,8 @@ import { AccountHolderSearchListItem } from './account-holder-search/types';
import { KeyInPaymentListItem } from './key-in/types'; import { KeyInPaymentListItem } from './key-in/types';
import { AccountHolderAuthListItem, AccountHolderAuthStatus } from './account-holder-auth/types'; import { AccountHolderAuthListItem, AccountHolderAuthStatus } from './account-holder-auth/types';
import { LinkContentType, LinkPaymentHistoryListItem, LinkPaymentSendMethod, LinkPaymentWaitListItem } from './link-pay/types'; import { LinkContentType, LinkPaymentHistoryListItem, LinkPaymentSendMethod, LinkPaymentWaitListItem } from './link-pay/types';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { PATHS } from '@/shared/constants/paths';
// ======================================== // ========================================
// 공통 Enums 및 타입들 // 공통 Enums 및 타입들
@@ -47,6 +49,20 @@ export enum Language {
EN = "EN" EN = "EN"
} }
export const SERVICE_MAP = [
{ code: 'SMS', serviceName: 'SMS 결제 통보', serviceDesc: '입금 요청부터 완료까지 SMS 자동 전송', icon: IMAGE_ROOT + '/icon_ing03.svg', path: PATHS.additionalService.smsPaymentNotification },
{ code: 'ARS', serviceName: '신용카드 ARS 결제', serviceDesc: '전화 한 통으로 결제 성공 편리하고 안전한 서비스', icon: IMAGE_ROOT + '/icon_ing01.svg', path: PATHS.additionalService.ars.list },
{ code: 'KEYIN', serviceName: 'KEY-IN 결제', serviceDesc: '상담 중 카드정보 입력으로 간편한 결제 지원', icon: IMAGE_ROOT + '/icon_ing02.svg', path: PATHS.additionalService.keyInPayment.list },
{ code: 'SEARCH_ACCOUNT_NAME', serviceName: '계좌성명조회', serviceDesc: '예금주 정보 입력으로 즉시 예금주 확인', icon: IMAGE_ROOT + '/icon_ing04.svg', path: PATHS.additionalService.accountHolderSearch.list },
{ code: 'PAYOUT', serviceName: '지급대행', serviceDesc: '하위 가맹점에 빠른 정산금 지급 지급대행 서비스', icon: IMAGE_ROOT + '/icon_ing05.svg', path: PATHS.additionalService.payout.list },
{ code: 'SETTLEMENT', serviceName: '정산대행', serviceDesc: '하위 가맹점 정산금 계산부터 지급까지 자동 해결 서비스', icon: IMAGE_ROOT + '/icon_ing06.svg', path: PATHS.additionalService.settlementAgency.manage },
{ code: 'LINKPAY', serviceName: '링크 결제', serviceDesc: '결제 링크 전송만으로 어디서든 결제 가능 서비스', icon: IMAGE_ROOT + '/icon_ing07.svg', path: PATHS.additionalService.linkPayment.shippingHistory },
{ code: 'FUND_ACCOUNT', serviceName: '자금이체', serviceDesc: '예치금으로 즉시 송금, 파일 등록만으로 다중 송금 가능', icon: IMAGE_ROOT + '/icon_ing08.svg', path: PATHS.additionalService.fundAccount.transferList },
{ code: 'ACCOUNT_AUTH', serviceName: '계좌점유인증', serviceDesc: '1원 송금으로 실제 계좌 점유 확인 여부', icon: IMAGE_ROOT + '/icon_ing09.svg', path: PATHS.additionalService.accountHolderAuth.list },
{ code: 'ALIMTALK', serviceName: '알림톡 결제통보', serviceDesc: '결제 상태를 알림톡으로 쉽고 빠른 안내', icon: IMAGE_ROOT + '/icon_ing10.svg', path: PATHS.additionalService.alimtalk.list },
{ code: 'FACE_AUTH', serviceName: '얼굴인증', serviceDesc: '얼굴 인식으로 간편 본인확인과 결제 가능한 안전 결제 서비스', icon: IMAGE_ROOT + '/icon_ing11.svg', path: PATHS.additionalService.faceAuth.list },
];
// ======================================== // ========================================
// 상세정보 Interface // 상세정보 Interface
// ======================================== // ========================================
@@ -223,8 +239,17 @@ export interface ExtensionListItemProps {
availableExtensionList: Array<availableExtensionListItem>; availableExtensionList: Array<availableExtensionListItem>;
} }
export interface ExtensionListResponse extends DefaulResponsePagination { export interface ExtensionListResponse {
content: Array<ExtensionListItemProps> activeExtensionList: string[];
availableExtensionList: string[];
}
export interface ExtensionCheckParams {
extensionCode: string;
}
export interface ExtensionCheckResponse {
checkResult: boolean;
} }
// ======================================== // ========================================

View File

@@ -1,7 +1,7 @@
import moment from 'moment'; import moment from 'moment';
import { IMAGE_ROOT } from '@/shared/constants/common'; import { IMAGE_ROOT } from '@/shared/constants/common';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { FilterSelect } from '@/shared/ui/filter/select'; import { FilterSelect } from '@/shared/ui/filter/select';
import { FilterCalendar } from '@/shared/ui/filter/calendar'; import { FilterCalendar } from '@/shared/ui/filter/calendar';
import { FilterButtonGroups } from '@/shared/ui/filter/button-groups'; import { FilterButtonGroups } from '@/shared/ui/filter/button-groups';
@@ -42,6 +42,9 @@ export const AccountHolderAuthFilter = ({
onClickToClose(); onClickToClose();
}; };
useEffect(() => {
setFilterAuthStatus(authStatus);
}, [authStatus]);
return ( return (
<> <>
<motion.div <motion.div

View File

@@ -49,8 +49,7 @@ export const ListDateGroup = ({
submallId={ items[i]?.submallId } submallId={ items[i]?.submallId }
settlementDate={ items[i]?.settlementDate } settlementDate={ items[i]?.settlementDate }
companyName={ items[i]?. companyName } companyName={ items[i]?. companyName }
disbursementStatus={ items[i]?.disbursementStatus } status={ items[i]?.status }
disbursementAmount={ items[i]?.disbursementAmount }
orderStatus={ items[i]?.orderStatus } orderStatus={ items[i]?.orderStatus }
arsPaymentMethod={ items[i]?.arsPaymentMethod } arsPaymentMethod={ items[i]?.arsPaymentMethod }

View File

@@ -12,30 +12,31 @@ import { getArsPaymentStatusName, getArsOrderStatusName } from '../model/ars/con
import { ServiceCode } from '../model/alimtalk/types'; import { ServiceCode } from '../model/alimtalk/types';
import { getAlimtalkAlimClText, getAlimtalkSendClTypeText, getAlimtalkSendTypeText, getAlimtalkServiceCodeText } from '../model/alimtalk/constant'; import { getAlimtalkAlimClText, getAlimtalkSendClTypeText, getAlimtalkSendTypeText, getAlimtalkServiceCodeText } from '../model/alimtalk/constant';
import { getAuthResultStatusText, getTransTypeText } from '../model/face-auth/constant'; import { getAuthResultStatusText, getTransTypeText } from '../model/face-auth/constant';
import { getPayoutStatusText } from '../model/payout/constant';
export const ListItem = ({ export const ListItem = ({
additionalServiceCategory, additionalServiceCategory,
mid, tid, paymentDate, paymentStatus, mid, tid, paymentDate, paymentStatus,
applicationDate, requestDate, bankName, accountNo, resultStatus, resultMessage, applicationDate, requestDate, bankName, accountNo, resultStatus, resultMessage,
amount, sendMethod, processStatus,registDate, amount, sendMethod, processStatus, registDate,
accountName, accountName,
submallId, settlementDate, companyName, submallId, settlementDate, companyName,
disbursementStatus, disbursementAmount, status: disbursementStatus, amount: disbursementAmount,
orderStatus, arsPaymentMethod, orderStatus, arsPaymentMethod,
alimCl, sendType, sendCl, alimCl, sendType, sendCl,
paymentMethod, receiverName, paymentMethod, receiverName,
requestId,subReqId, requestId, subReqId,
buyerName,receiverInfo, buyerName, receiverInfo,
seq,serviceCode,sendDate, seq, serviceCode, sendDate,
authStatus, authStatus, status,
smsCl,groupId,userMallId,transType, smsCl, groupId, userMallId, transType,
authResult,failReason,requestTime, authResult, failReason, requestTime,
onResendClick onResendClick
}: ListItemProps) => { }: ListItemProps) => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
@@ -89,7 +90,7 @@ export const ListItem = ({
} }
} }
else if (additionalServiceCategory === AdditionalServiceCategory.Alimtalk) { else if (additionalServiceCategory === AdditionalServiceCategory.Alimtalk) {
if (sendCl === "SUCCESS" || "REQUEST") { if (sendCl === "SUCCESS" || sendCl === "REQUEST") {
rs = 'blue'; rs = 'blue';
} else { } else {
rs = 'gray'; rs = 'gray';
@@ -104,20 +105,13 @@ export const ListItem = ({
} }
} }
else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentHistory) { else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentHistory) {
if (paymentStatus === "PAYMENT_COMPLETE") { if (paymentStatus === "0" || paymentStatus === "1" || paymentStatus === "2") {
rs = 'blue'; rs = 'blue';
} }
else if (paymentStatus === "3") {
else if (paymentStatus === "ACTIVE") { rs = 'gray';
rs = 'blue';
} }
else if (paymentStatus === "DEPOSIT_REQUEST") { else if (paymentStatus === '4') {
rs = 'blue';
}
else if (paymentStatus === "PAYMENT_FAIL") {
rs = 'blue';
}
else if (paymentStatus === "INACTIVE") {
rs = 'gray'; rs = 'gray';
} }
} else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentWait) { } else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentWait) {
@@ -144,6 +138,12 @@ export const ListItem = ({
} else if (resultStatus === "FAIL") { } else if (resultStatus === "FAIL") {
rs = 'gray'; rs = 'gray';
} }
} else if (additionalServiceCategory === AdditionalServiceCategory.Payout) {
if (status === "SUCCESS" || status === "REQUEST") {
rs = 'blue';
} else if (status === "FAIL") {
rs = 'gray';
}
} }
return rs; return rs;
@@ -286,7 +286,7 @@ export const ListItem = ({
if (applicationDate && applicationDate.length >= 12) { if (applicationDate && applicationDate.length >= 12) {
timeStr = applicationDate.substring(8, 10) + ':' + applicationDate.substring(10, 12); timeStr = applicationDate.substring(8, 10) + ':' + applicationDate.substring(10, 12);
} else { } else {
timeStr = requestDate?.substring(8,10) + ':' + requestDate?.substring(10, 12); timeStr = requestDate?.substring(8, 10) + ':' + requestDate?.substring(10, 12);
} }
} }
else if (additionalServiceCategory === AdditionalServiceCategory.Ars) { else if (additionalServiceCategory === AdditionalServiceCategory.Ars) {
@@ -349,9 +349,10 @@ export const ListItem = ({
else if (additionalServiceCategory === AdditionalServiceCategory.FaceAuth) { else if (additionalServiceCategory === AdditionalServiceCategory.FaceAuth) {
str = `${userMallId}(${mid})`; str = `${userMallId}(${mid})`;
} }
else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentHistory || else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentHistory
additionalServiceCategory === AdditionalServiceCategory.LinkPaymentWait
) { ) {
str = `${buyerName}`;
} else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentWait) {
str = `${buyerName}(${receiverInfo})`; str = `${buyerName}(${receiverInfo})`;
} }
else if (additionalServiceCategory === AdditionalServiceCategory.Payout) { else if (additionalServiceCategory === AdditionalServiceCategory.Payout) {
@@ -451,7 +452,7 @@ export const ListItem = ({
else if (additionalServiceCategory === AdditionalServiceCategory.Payout) { else if (additionalServiceCategory === AdditionalServiceCategory.Payout) {
rs.push( rs.push(
<div className="transaction-details"> <div className="transaction-details">
<span>{disbursementStatus}</span> <span>{getPayoutStatusText(disbursementStatus)}</span>
<span className="separator">|</span> <span className="separator">|</span>
<span>{submallId}</span> <span>{submallId}</span>
</div> </div>
@@ -498,7 +499,7 @@ export const ListItem = ({
); );
} }
else if (additionalServiceCategory === AdditionalServiceCategory.Alimtalk) { else if (additionalServiceCategory === AdditionalServiceCategory.Alimtalk) {
console.log(serviceCode) console.log(serviceCode)
rs.push( rs.push(
<div className="transaction-details"> <div className="transaction-details">
<span>{getTime()}</span> <span>{getTime()}</span>

View File

@@ -0,0 +1,90 @@
import { BottomSheetMotionDuration, BottomSheetMotionVaiants } from '@/entities/common/model/constant';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { motion } from 'framer-motion';
export interface DownloadTypeBottomSheetProps {
bottomSheetOn: boolean;
setBottomSheetOn: (bottomSheetOn: boolean) => void;
onSelectType: (type: 'IMAGE' | 'EMAIL') => void;
}
export const DownloadTypeBottomSheet = ({
bottomSheetOn,
setBottomSheetOn,
onSelectType
}: DownloadTypeBottomSheetProps) => {
const onClickToClose = () => {
setBottomSheetOn(false);
};
const handleSelectType = (type: 'IMAGE' | 'EMAIL') => {
onSelectType(type);
setBottomSheetOn(false);
};
return (
<>
{ (bottomSheetOn) &&
<div className="bg-dim"></div>
}
<motion.div
className="bottomsheet"
initial="hidden"
animate={ (bottomSheetOn)? 'visible': 'hidden' }
variants={ BottomSheetMotionVaiants }
transition={ BottomSheetMotionDuration }
>
<div className="bottomsheet-header">
<div className="bottomsheet-title">
<h2> </h2>
<button
className="close-btn"
type="button"
>
<img
src={ IMAGE_ROOT + '/ico_close.svg' }
alt="닫기"
onClick={ () => onClickToClose() }
/>
</button>
</div>
</div>
<div className="bottomsheet-content">
<div className="email-section">
<div
className="email-label mb-10"
onClick={() => handleSelectType('IMAGE')}
style={{ cursor: 'pointer' }}
>
<div className="mail-icon">
<div className="mail-icon-bg"></div>
<img
src={ IMAGE_ROOT + '/ico_pic.svg' }
alt="이미지"
/>
</div>
<span className="label-text"> </span>
</div>
<div
className="email-label"
onClick={() => handleSelectType('EMAIL')}
style={{ cursor: 'pointer' }}
>
<div className="mail-icon">
<div className="mail-icon-bg"></div>
<img
src={ IMAGE_ROOT + '/ico_email.svg' }
alt='메일'
/>
</div>
<span className="label-text"> </span>
</div>
</div>
</div>
</motion.div>
</>
);
};

View File

@@ -22,12 +22,18 @@ import { useStore } from '@/shared/model/store';
import { AccountHolderAuthListItem, AccountHolderAuthStatus, ExtensionAccountHolderAuthContentItem, ExtensionAccountHolderAuthDownloadExcelParams, ExtensionAccountHolderAuthDownloadExcelResponse } from '@/entities/additional-service/model/account-holder-auth/types'; import { AccountHolderAuthListItem, AccountHolderAuthStatus, ExtensionAccountHolderAuthContentItem, ExtensionAccountHolderAuthDownloadExcelParams, ExtensionAccountHolderAuthDownloadExcelResponse } from '@/entities/additional-service/model/account-holder-auth/types';
import { AdditionalServiceCategory } from '@/entities/additional-service/model/types'; import { AdditionalServiceCategory } from '@/entities/additional-service/model/types';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet'; import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const AccountHolderAuthPage = () => { export const AccountHolderAuthPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid; const userMid = useStore.getState().UserStore.mid;
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'ACCOUNT_AUTH'
});
const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST); const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST);
const [listItems, setListItems] = useState<Array<ExtensionAccountHolderAuthContentItem>>([]); const [listItems, setListItems] = useState<Array<ExtensionAccountHolderAuthContentItem>>([]);
const [filterOn, setFilterOn] = useState<boolean>(false); const [filterOn, setFilterOn] = useState<boolean>(false);
@@ -115,6 +121,10 @@ export const AccountHolderAuthPage = () => {
authStatus authStatus
]); ]);
if (!hasAccess) {
return <AccessDeniedDialog />;
}
return ( return (
<> <>
<main> <main>

View File

@@ -83,7 +83,7 @@ export const AccountHolderAuthDetailPage = () => {
</li> </li>
<li className="kv-row"> <li className="kv-row">
<span className="k"></span> <span className="k"></span>
<span className="v">{detail?.bankName}</span> <span className="v">{detail?.accountNo}</span>
</li> </li>
<li className="kv-row"> <li className="kv-row">
<span className="k"></span> <span className="k"></span>

View File

@@ -22,12 +22,18 @@ import { useStore } from '@/shared/model/store';
import { AccountHolderSearchListItem, AccountHolderSearchCl, AccountHolderResultStatus } from '@/entities/additional-service/model/account-holder-search/types'; import { AccountHolderSearchListItem, AccountHolderSearchCl, AccountHolderResultStatus } from '@/entities/additional-service/model/account-holder-search/types';
import { resultStatusBtnGroup } from '@/entities/additional-service/model/account-holder-search/constant'; import { resultStatusBtnGroup } from '@/entities/additional-service/model/account-holder-search/constant';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet'; import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const AccountHolderSearchPage = () => { export const AccountHolderSearchPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid; const userMid = useStore.getState().UserStore.mid;
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'SEARCH_ACCOUNT_NAME'
});
const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST); const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST);
const [listItems, setListItems] = useState<Array<AccountHolderSearchListItem>>([]); const [listItems, setListItems] = useState<Array<AccountHolderSearchListItem>>([]);
const [pageParam, setPageParam] = useState<DefaultRequestPagination>(DEFAULT_PAGE_PARAM); const [pageParam, setPageParam] = useState<DefaultRequestPagination>(DEFAULT_PAGE_PARAM);
@@ -125,6 +131,10 @@ export const AccountHolderSearchPage = () => {
resultStatus resultStatus
]); ]);
if (!hasAccess) {
return <AccessDeniedDialog />;
}
return ( return (
<> <>
<main> <main>

View File

@@ -18,7 +18,7 @@ export const AccountHolderSearchRequestPage = () => {
const midOptions = useStore.getState().UserStore.selectOptionsMids const midOptions = useStore.getState().UserStore.selectOptionsMids
const bankList = useStore.getState().CommonStore.bankList const bankList = useStore.getState().CommonStore.bankList
useSetHeaderTitle('계좌성명조회_신청'); useSetHeaderTitle('계좌성명조회 신청');
useSetHeaderType(HeaderType.LeftArrow); useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false); useSetFooterMode(false);
useSetOnBack(() => { useSetOnBack(() => {

View File

@@ -12,7 +12,7 @@ import { useEffect, useState } from 'react';
import { ExtensionAlimtalkDetailParams, ExtensionAlimtalkDetailResponse } from '@/entities/additional-service/model/alimtalk/types'; import { ExtensionAlimtalkDetailParams, ExtensionAlimtalkDetailResponse } from '@/entities/additional-service/model/alimtalk/types';
import { useExtensionAlimtalkDetailMutation } from '@/entities/additional-service/api/alimtalk/use-extansion-alimtalk-detail-mutation'; import { useExtensionAlimtalkDetailMutation } from '@/entities/additional-service/api/alimtalk/use-extansion-alimtalk-detail-mutation';
import moment from 'moment'; import moment from 'moment';
import { getAlimtalkAlimClText, getAlimtalkSendClTypeText, getAlimtalkSendTypeText } from '@/entities/additional-service/model/alimtalk/constant'; import { getAlimtalkAlimClText, getAlimtalkSendClTypeText, getAlimtalkSendTypeText, getAlimtalkServiceCodeText } from '@/entities/additional-service/model/alimtalk/constant';
export const AlimtalkDetailPage = () => { export const AlimtalkDetailPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
@@ -60,7 +60,7 @@ export const AlimtalkDetailPage = () => {
<div className="num-amount"> <div className="num-amount">
<span className="amount">{ detail?.receiverName }</span> <span className="amount">{ detail?.receiverName }</span>
</div> </div>
<div className="num-store">{ detail?.merchantName }</div> <div className="num-store">{ detail?.companyName }</div>
<div className="num-day">{ getDate(detail?.sendDate) }</div> <div className="num-day">{ getDate(detail?.sendDate) }</div>
</div> </div>
<div className="detail-divider"></div> <div className="detail-divider"></div>
@@ -89,7 +89,7 @@ export const AlimtalkDetailPage = () => {
</li> </li>
<li className="kv-row"> <li className="kv-row">
<span className="k"></span> <span className="k"></span>
<span className="v">{ detail?.paymentMethod }</span> <span className="v">{ getAlimtalkServiceCodeText(detail?.serviceCode) }</span>
</li> </li>
<li className="kv-row"> <li className="kv-row">
<span className="k"></span> <span className="k"></span>

View File

@@ -31,11 +31,17 @@ import { useStore } from '@/shared/model/store';
import { snackBar } from '@/shared/lib'; import { snackBar } from '@/shared/lib';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet'; import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { AlimtalkFilter } from '@/entities/additional-service/ui/filter/alimtalk-filter'; import { AlimtalkFilter } from '@/entities/additional-service/ui/filter/alimtalk-filter';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const AlimtalkListPage = () => { export const AlimtalkListPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid; const userMid = useStore.getState().UserStore.mid;
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'ALIMTALK'
});
const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST); const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST);
const [listItems, setListItems] = useState<Array<AlimtalkListContent>>([]); const [listItems, setListItems] = useState<Array<AlimtalkListContent>>([]);
const [filterOn, setFilterOn] = useState<boolean>(false); const [filterOn, setFilterOn] = useState<boolean>(false);
@@ -182,6 +188,10 @@ export const AlimtalkListPage = () => {
sendCl sendCl
]); ]);
if (!hasAccess) {
return <AccessDeniedDialog />;
}
return ( return (
<> <>
<main> <main>

View File

@@ -165,9 +165,9 @@ export const AlimtalkSettingPage = () => {
></AlimTalkSettingServiceRow> ></AlimTalkSettingServiceRow>
<AlimTalkSettingServiceRow <AlimTalkSettingServiceRow
title='신용카드(취소)' title='신용카드(취소)'
merchantFlag={merchantBankApprovalFlag} merchantFlag={merchantCardCancelFlag}
userFlag={userCardCancelFlag} userFlag={userCardCancelFlag}
setMerchantFlag={setMerchantBankApprovalFlag} setMerchantFlag={setMerchantCardCancelFlag}
setUserFlag={setUserCardCancelFlag} setUserFlag={setUserCardCancelFlag}
></AlimTalkSettingServiceRow> ></AlimTalkSettingServiceRow>
<AlimTalkSettingServiceRow <AlimTalkSettingServiceRow

View File

@@ -21,12 +21,18 @@ import { ArsPaymentStatusBtnGroup } from '@/entities/additional-service/model/ar
import { ArsFilter } from '@/entities/additional-service/ui/filter/ars-filter'; import { ArsFilter } from '@/entities/additional-service/ui/filter/ars-filter';
import { useStore } from '@/shared/model/store'; import { useStore } from '@/shared/model/store';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet'; import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const ArsListPage = () => { export const ArsListPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid; const userMid = useStore.getState().UserStore.mid;
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'ARS'
});
const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST); const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST);
const [listItems, setListItems] = useState<Array<ArsListContent>>([]); const [listItems, setListItems] = useState<Array<ArsListContent>>([]);
const [filterOn, setFilterOn] = useState<boolean>(false); const [filterOn, setFilterOn] = useState<boolean>(false);
@@ -119,9 +125,6 @@ export const ArsListPage = () => {
setPaymentStatus(val); setPaymentStatus(val);
}; };
useEffect(() => {
callList();
}, []);
useEffect(() => { useEffect(() => {
callList(); callList();
}, [ }, [
@@ -181,6 +184,10 @@ export const ArsListPage = () => {
return rs; return rs;
} }
// if (!hasAccess) {
// return <AccessDeniedDialog />;
// }
return ( return (
<> <>
<main> <main>

View File

@@ -22,11 +22,17 @@ import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { useExtensionFaceAuthListtMutation } from '@/entities/additional-service/api/face-auth/use-extension-face-auth-list-mutation'; import { useExtensionFaceAuthListtMutation } from '@/entities/additional-service/api/face-auth/use-extension-face-auth-list-mutation';
import { useExtensionFaceAuthDownloadExcelMutation } from '@/entities/additional-service/api/face-auth/use-extension-face-auth-download-excel-mutation'; import { useExtensionFaceAuthDownloadExcelMutation } from '@/entities/additional-service/api/face-auth/use-extension-face-auth-download-excel-mutation';
import { ListDateGroup } from '@/entities/additional-service/ui/list-date-group'; import { ListDateGroup } from '@/entities/additional-service/ui/list-date-group';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const FaceAuthPage = () => { export const FaceAuthPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid; const userMid = useStore.getState().UserStore.mid;
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'FACE_AUTH'
});
const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST); const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST);
const [listItems, setListItems] = useState<Array<FaceAuthListItem>>([]); const [listItems, setListItems] = useState<Array<FaceAuthListItem>>([]);
const [filterOn, setFilterOn] = useState<boolean>(false); const [filterOn, setFilterOn] = useState<boolean>(false);
@@ -54,7 +60,7 @@ export const FaceAuthPage = () => {
authResult?: FaceAuthResult authResult?: FaceAuthResult
}) => { }) => {
let params: ExtensionFaceAuthListParams = { let params: ExtensionFaceAuthListParams = {
mid: mid, mid: "faceauth0m",
userMallId: userMallId, userMallId: userMallId,
fromDate: fromDate, fromDate: fromDate,
toDate: toDate, toDate: toDate,
@@ -80,7 +86,7 @@ export const FaceAuthPage = () => {
const onSendRequest = (selectedEmail?: string) => { const onSendRequest = (selectedEmail?: string) => {
if (selectedEmail) { if (selectedEmail) {
const params: ExtensionFaceAuthExcelDownlaodPrams = { const params: ExtensionFaceAuthExcelDownlaodPrams = {
mid: mid, mid: "faceauth0m",
email: selectedEmail, email: selectedEmail,
fromDate: fromDate, fromDate: fromDate,
toDate: toDate toDate: toDate
@@ -106,9 +112,9 @@ export const FaceAuthPage = () => {
}; };
const getListDateGroup = () => { const getListDateGroup = () => {
let rs= []; let rs = [];
let date = ''; let date = '';
let list= []; let list = [];
for (let i = 0; i < listItems.length; i++) { for (let i = 0; i < listItems.length; i++) {
let item = listItems[i]; let item = listItems[i];
if (!!item) { if (!!item) {
@@ -155,6 +161,10 @@ export const FaceAuthPage = () => {
callList(); callList();
}, [mid, userMallId, fromDate, toDate, transType, authResult]); }, [mid, userMallId, fromDate, toDate, transType, authResult]);
// if (!hasAccess) {
// return <AccessDeniedDialog />;
// }
return ( return (
<> <>
<main> <main>

View File

@@ -1,20 +1,24 @@
import { useState } from 'react'; import { useState } from 'react';
import { PATHS } from '@/shared/constants/paths'; import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate'; import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { FundAccountTab } from '@/entities/additional-service/ui/fund-account/tab'; import { FundAccountTab } from '@/entities/additional-service/ui/fund-account/tab';
import { FundAccountTransferListWrap } from '@/entities/additional-service/ui/fund-account/transfer-list-wrap'; import { FundAccountTransferListWrap } from '@/entities/additional-service/ui/fund-account/transfer-list-wrap';
import { FundAccountTabKeys } from '@/entities/additional-service/model/fund-account/types'; import { FundAccountTabKeys } from '@/entities/additional-service/model/fund-account/types';
import { HeaderType } from '@/entities/common/model/types'; import { HeaderType } from '@/entities/common/model/types';
import { import {
useSetHeaderTitle, useSetHeaderTitle,
useSetHeaderType, useSetHeaderType,
useSetFooterMode, useSetFooterMode,
useSetOnBack useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout'; } from '@/widgets/sub-layout/use-sub-layout';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const FundAccountTransferListPage = () => { export const FundAccountTransferListPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'FUND_ACCOUNT'
});
const [activeTab, setActiveTab] = useState<FundAccountTabKeys>(FundAccountTabKeys.TransferList); const [activeTab, setActiveTab] = useState<FundAccountTabKeys>(FundAccountTabKeys.TransferList);
useSetHeaderTitle('자금이체'); useSetHeaderTitle('자금이체');
@@ -24,12 +28,16 @@ export const FundAccountTransferListPage = () => {
navigate(PATHS.home); navigate(PATHS.home);
}); });
if (!hasAccess) {
return <AccessDeniedDialog />;
}
return ( return (
<> <>
<main> <main>
<div className="tab-content"> <div className="tab-content">
<div className="tab-pane pt-46 active"> <div className="tab-pane pt-46 active">
<FundAccountTab activeTab={ activeTab }></FundAccountTab> <FundAccountTab activeTab={activeTab}></FundAccountTab>
<FundAccountTransferListWrap></FundAccountTransferListWrap> <FundAccountTransferListWrap></FundAccountTransferListWrap>
</div> </div>
</div> </div>

View File

@@ -138,7 +138,7 @@ export const FundAccountTransferRequestPage = () => {
<input <input
type="text" type="text"
value={accountName} value={accountName}
onChange={(e) => setAccountName(e.target.value)} onChange={(e: ChangeEvent<HTMLInputElement>) => setAccountName(e.target.value) }
/> />
</div> </div>
</div> </div>
@@ -159,7 +159,7 @@ export const FundAccountTransferRequestPage = () => {
<input <input
type="text" type="text"
value={moid} value={moid}
onChange={(e) => setMoid(e.target.value)} onChange={(e: ChangeEvent<HTMLInputElement>) => setMoid(e.target.value)}
/> />
</div> </div>
</div> </div>

View File

@@ -22,11 +22,17 @@ import { useStore } from '@/shared/model/store';
import { KeyInPaymentListItem, KeyInPaymentPaymentStatus } from '@/entities/additional-service/model/key-in/types'; import { KeyInPaymentListItem, KeyInPaymentPaymentStatus } from '@/entities/additional-service/model/key-in/types';
import { keyInPaymentPaymentStatusBtnGroup } from '@/entities/additional-service/model/key-in/constant'; import { keyInPaymentPaymentStatusBtnGroup } from '@/entities/additional-service/model/key-in/constant';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet'; import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const KeyInPaymentPage = () => { export const KeyInPaymentPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid; const userMid = useStore.getState().UserStore.mid;
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'KEYIN'
});
const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST); const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST);
const [listItems, setListItems] = useState<Array<KeyInPaymentListItem>>([]); const [listItems, setListItems] = useState<Array<KeyInPaymentListItem>>([]);
const [filterOn, setFilterOn] = useState<boolean>(false); const [filterOn, setFilterOn] = useState<boolean>(false);
@@ -136,6 +142,11 @@ export const KeyInPaymentPage = () => {
maxAmount maxAmount
]); ]);
// if (!hasAccess) {
// return <AccessDeniedDialog />;
// }
return ( return (
<> <>
<main> <main>

View File

@@ -12,6 +12,7 @@ import {
useSetOnBack useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout'; } from '@/widgets/sub-layout/use-sub-layout';
import { LinkPaymentTabKeys } from '@/entities/additional-service/model/link-pay/types'; import { LinkPaymentTabKeys } from '@/entities/additional-service/model/link-pay/types';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
/** /**
* 발송내역 탭 화면 * 발송내역 탭 화면
@@ -20,6 +21,11 @@ import { LinkPaymentTabKeys } from '@/entities/additional-service/model/link-pay
export const LinkPaymentHistoryPage = () => { export const LinkPaymentHistoryPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'LINKPAY'
});
const [activeTab, setActiveTab] = useState<LinkPaymentTabKeys>(LinkPaymentTabKeys.ShippingHistory) const [activeTab, setActiveTab] = useState<LinkPaymentTabKeys>(LinkPaymentTabKeys.ShippingHistory)
useSetHeaderTitle('링크결제') useSetHeaderTitle('링크결제')
@@ -29,6 +35,10 @@ export const LinkPaymentHistoryPage = () => {
navigate(PATHS.home); navigate(PATHS.home);
}); });
if (!hasAccess) {
return <AccessDeniedDialog />;
}
return ( return (
<> <>
<main> <main>

View File

@@ -16,7 +16,8 @@ import { AdditionalServiceCategory, DetailResponse, PaymentInfo, TitleInfo } fro
import { useExtensionLinkPayWaitDetailMutation, } from '@/entities/additional-service/api/link-payment/use-extension-link-pay-wait-detail-mutation'; import { useExtensionLinkPayWaitDetailMutation, } from '@/entities/additional-service/api/link-payment/use-extension-link-pay-wait-detail-mutation';
import { PaymentInfoWrap } from '@/entities/additional-service/ui/info-wrap/payment-info-wrap'; import { PaymentInfoWrap } from '@/entities/additional-service/ui/info-wrap/payment-info-wrap';
import { useExtensionLinkPayWaitDeleteMutation } from '@/entities/additional-service/api/link-payment/use-extension-link-pay-wait-delete-mutation'; import { useExtensionLinkPayWaitDeleteMutation } from '@/entities/additional-service/api/link-payment/use-extension-link-pay-wait-delete-mutation';
import { ExtensionLinkPayWaitDeleteParams, ExtensionLinkPayWaitDetailParams } from '@/entities/additional-service/model/link-pay/types'; import { ExtensionLinkPayWaitDeleteParams, ExtensionLinkPayWaitDetailParams, LinkPaymentProcessStatus } from '@/entities/additional-service/model/link-pay/types';
import { snackBar } from '@/shared/lib';
export const LinkPaymentWaitDetailPage = () => { export const LinkPaymentWaitDetailPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
@@ -54,13 +55,13 @@ export const LinkPaymentWaitDetailPage = () => {
requestId: requestId requestId: requestId
} }
linkPayWaitDelete(deleteParam) linkPayWaitDelete(deleteParam)
.then((response) => { .then((rs) => {
console.log("Delete 성공 응답: ", response)
onClickToNavigate(PATHS.additionalService.linkPayment.pendingSend) onClickToNavigate(PATHS.additionalService.linkPayment.pendingSend)
snackBar("삭제를 성공하였습니다.")
}) })
.catch((error) => { .catch((error) => {
console.error("Resend 실패: ", error); console.error("Resend 실패: ", error);
snackBar(`[실패] ${error?.response?.data?.message}`)
}); });
} }
@@ -120,6 +121,7 @@ export const LinkPaymentWaitDetailPage = () => {
<button <button
className="btn-50 btn-blue flex-1" className="btn-50 btn-blue flex-1"
onClick={() => onClickToCancel()} onClick={() => onClickToCancel()}
disabled={paymentInfo?.processStatus !== LinkPaymentProcessStatus.SEND_REQUEST}
></button> ></button>
</div> </div>
</div> </div>

View File

@@ -2,16 +2,16 @@ import { ChangeEvent, useEffect, useState } from 'react';
import { PATHS } from '@/shared/constants/paths'; import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate'; import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { HeaderType } from '@/entities/common/model/types'; import { HeaderType } from '@/entities/common/model/types';
import { IMAGE_ROOT } from '@/shared/constants/common'; import {
import { useSetHeaderTitle,
useSetHeaderTitle, useSetHeaderType,
useSetHeaderType, useSetFooterMode,
useSetFooterMode,
useSetOnBack useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout'; } from '@/widgets/sub-layout/use-sub-layout';
import { useExtensionListMutation } from '@/entities/additional-service/api/use-extension-list-mutation'; import { useExtensionListMutation } from '@/entities/additional-service/api/use-extension-list-mutation';
import { ExtensionListParams, ExtensionListResponse } from '@/entities/additional-service/model/types'; import { ExtensionListParams, ExtensionListResponse, SERVICE_MAP } from '@/entities/additional-service/model/types';
import { useStore } from '@/shared/model/store'; import { useStore } from '@/shared/model/store';
import { Dialog } from '@/shared/ui/dialogs/dialog';
export const ListPage = () => { export const ListPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
@@ -19,6 +19,9 @@ export const ListPage = () => {
const userMid = useStore.getState().UserStore.mid; const userMid = useStore.getState().UserStore.mid;
const [mid, setMid] = useState<string>(userMid); const [mid, setMid] = useState<string>(userMid);
const [activeServices, setActiveServices] = useState<string[]>([]);
const [availableServices, setAvailableServices] = useState<string[]>([]);
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
const { mutateAsync: extensionList } = useExtensionListMutation(); const { mutateAsync: extensionList } = useExtensionListMutation();
@@ -34,112 +37,63 @@ export const ListPage = () => {
mid: mid mid: mid
} }
extensionList(params).then((rs: ExtensionListResponse) => { extensionList(params).then((rs: ExtensionListResponse) => {
console.log(rs) setActiveServices(rs.activeExtensionList || []);
setAvailableServices(rs.availableExtensionList || []);
}); });
}; };
const activeExtensionList = [
{
className: 'list-wrap01', serviceName: 'SMS 결제 통보', serviceDesc: '입금 요청부터 완료까지 SMS 자동 전송',
icon: IMAGE_ROOT + '/icon_ing03.svg', path: PATHS.additionalService.smsPaymentNotification
},
{
className: 'list-wrap01', serviceName: '신용카드 ARS 결제', serviceDesc: '전화 한 통으로 결제 성공 편리하고 안전한 서비스',
icon: IMAGE_ROOT + '/icon_ing01.svg', path: PATHS.additionalService.ars.list
},
{
className: 'list-wrap01', serviceName: 'KEY-IN 결제', serviceDesc: '상담 중 카드정보 입력으로 간편한 결제 지원',
icon: IMAGE_ROOT + '/icon_ing02.svg', path: PATHS.additionalService.keyInPayment.list
},
{
className: 'list-wrap01', serviceName: '계좌성명조회', serviceDesc: '예금주 정보 입력으로 즉시 예금주 확인',
icon: IMAGE_ROOT + '/icon_ing04.svg', path: PATHS.additionalService.accountHolderSearch.list
},
];
const availableExtensionList = [
{
className: 'list-wrap02', serviceName: '지급대행', serviceDesc: '하위 가맹점에 빠른 정산금 지급 지급대행 서비스',
icon: IMAGE_ROOT + '/icon_ing05.svg', path: PATHS.additionalService.payout.list
},
{
className: 'list-wrap02', serviceName: '정산대행', serviceDesc: '하위 가맹점 정산금 계산부터 지급까지 자동 해결 서비스',
icon: IMAGE_ROOT + '/icon_ing06.svg', path: PATHS.additionalService.settlementAgency.manage
},
{
className: 'list-wrap02', serviceName: '링크 결제', serviceDesc: '결제 링크 전송만으로 어디서든 결제 가능 서비스',
icon: IMAGE_ROOT + '/icon_ing07.svg', path: PATHS.additionalService.linkPayment.shippingHistory
},
{
className: 'list-wrap02', serviceName: '자금이체', serviceDesc: '예치금으로 즉시 송금, 파일 등록만으로 다중 송금 가능',
icon: IMAGE_ROOT + '/icon_ing08.svg', path: PATHS.additionalService.fundAccount.transferList
},
{
className: 'list-wrap02', serviceName: '계좌점유인증', serviceDesc: '1원 송금으로 실제 계좌 점유 확인 여부',
icon: IMAGE_ROOT + '/icon_ing09.svg', path: PATHS.additionalService.accountHolderAuth.list
},
{
className: 'list-wrap02', serviceName: '알림톡 결제통보', serviceDesc: '결제 상태를 알림톡으로 쉽고 빠른 안내',
icon: IMAGE_ROOT + '/icon_ing10.svg', path: PATHS.additionalService.alimtalk.list
},
];
const onClickToNavigate = (path?: string) => {
if(!!path){
navigate(path);
}
};
const getActiveExtensionList = () => { const getActiveExtensionList = () => {
let rs = []; const filteredServices = SERVICE_MAP.filter(service =>
for(let i=0;i<activeExtensionList.length;i++){ activeServices.includes(service.code)
rs.push( );
<div
key={ 'key-active-' + i } return filteredServices.map((service) => (
className={ activeExtensionList[i]?.className } <div
onClick={ () => onClickToNavigate(activeExtensionList[i]?.path) } key={'key-active-' + service.code}
> className="list-wrap01"
<div> onClick={() => service.path && navigate(service.path)}
<div className="service-name">{ activeExtensionList[i]?.serviceName }</div> >
<p className="service-desc">{ activeExtensionList[i]?.serviceDesc }</p> <div>
</div> <div className="service-name">{service.serviceName}</div>
<img <p className="service-desc">{service.serviceDesc}</p>
src={ activeExtensionList[i]?.icon }
alt={ activeExtensionList[i]?.serviceName }
/>
</div> </div>
); <img
} src={service.icon}
return rs; alt={service.serviceName}
/>
</div>
));
}; };
const getAvailableExtensionList = () => { const getAvailableExtensionList = () => {
let rs = []; const filteredServices = SERVICE_MAP.filter(service =>
for(let i=0;i<availableExtensionList.length;i++){ availableServices.includes(service.code)
rs.push( );
<div
key={ 'key-available-' + i } return filteredServices.map((service) => (
className={ availableExtensionList[i]?.className } <div
onClick={ () => onClickToNavigate(availableExtensionList[i]?.path) } key={'key-available-' + service.code}
> className="list-wrap02"
<div> onClick={() => setDialogOpen(true)}
<div className="service-name">{ availableExtensionList[i]?.serviceName }</div> >
<p className="service-desc">{ availableExtensionList[i]?.serviceDesc }</p> <div>
</div> <div className="service-name">{service.serviceName}</div>
<img <p className="service-desc">{service.serviceDesc}</p>
src={ availableExtensionList[i]?.icon }
alt={ availableExtensionList[i]?.serviceName }
/>
</div> </div>
); <img
} src={service.icon}
return rs; alt={service.serviceName}
/>
</div>
));
}; };
useEffect(() => { useEffect(() => {
if(!!mid){ if (!!mid) {
callExtensionList(); callExtensionList();
} }
}, [mid]); }, [mid]);
return ( return (
<> <>
<main> <main>
@@ -147,28 +101,41 @@ export const ListPage = () => {
<div className="tab-pane sub active"> <div className="tab-pane sub active">
<div className="ing-list"> <div className="ing-list">
<div className="input-wrapper top-select"> <div className="input-wrapper top-select">
<select <select
value={ mid } value={mid}
onChange={ (e: ChangeEvent<HTMLSelectElement>) => setMid(e.target.value) } onChange={(e: ChangeEvent<HTMLSelectElement>) => setMid(e.target.value)}
> >
{ {
midOptions.map((value, index) => ( midOptions.map((value) => (
<option <option
key={ value.value } key={value.value}
value={ value.value } value={value.value}
>{ value.name }</option> >{value.name}</option>
)) ))
} }
</select> </select>
</div> </div>
<h3 className="ing-title"> </h3> <h3 className="ing-title"> </h3>
{ getActiveExtensionList() } {getActiveExtensionList()}
<h3 className="ing-title"> </h3> <h3 className="ing-title"> </h3>
{ getAvailableExtensionList() } {getAvailableExtensionList()}
</div> </div>
</div> </div>
</div> </div>
</main> </main>
<Dialog
open={dialogOpen}
onClose={() => setDialogOpen(false)}
message={
<>
.<br />
.
</>
}
buttonLabel={['확인']}
onConfirmClick={() => setDialogOpen(false)}
afterLeave={() => { }}
/>
</> </>
) )
}; };

View File

@@ -13,6 +13,9 @@ import { ExtensionPayoutDetailDownloadCertificateParams, ExtensionPayoutDetailDo
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { NumericFormat } from 'react-number-format'; import { NumericFormat } from 'react-number-format';
import { useExtensionPayoutDetailDownloadCertificateMutation } from '@/entities/additional-service/api/payout/use-extension-payout-detail-download-cetificate-mutation'; import { useExtensionPayoutDetailDownloadCertificateMutation } from '@/entities/additional-service/api/payout/use-extension-payout-detail-download-cetificate-mutation';
import moment from 'moment';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { DownloadTypeBottomSheet } from '@/entities/common/ui/download-type-bottom-sheet';
export const PayoutDetailPage = () => { export const PayoutDetailPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
@@ -21,9 +24,9 @@ export const PayoutDetailPage = () => {
const tid = location.state.tid; const tid = location.state.tid;
const mid = location.state.mid; const mid = location.state.mid;
const [requestType, setRequestType] = useState<string>('');
const [email, setEmail] = useState<string>('');
const [detail, setDetail] = useState<ExtensionPayoutDetailResponse>(); const [detail, setDetail] = useState<ExtensionPayoutDetailResponse>();
const [downloadTypeBottomSheetOn, setDownloadTypeBottomSheetOn] = useState<boolean>(false);
const [emailBottomSheetOn, setEmailBottomSheetOn] = useState<boolean>(false);
const { mutateAsync: extensionPayoutDetail } = useExtensionPayoutDetailMutation(); const { mutateAsync: extensionPayoutDetail } = useExtensionPayoutDetailMutation();
const { mutateAsync: extensionPayoutDetailDownloadCertification } = useExtensionPayoutDetailDownloadCertificateMutation(); const { mutateAsync: extensionPayoutDetailDownloadCertification } = useExtensionPayoutDetailDownloadCertificateMutation();
@@ -47,15 +50,48 @@ export const PayoutDetailPage = () => {
}); });
const onClickToDownload = () => { const onClickToDownload = () => {
let params: ExtensionPayoutDetailDownloadCertificateParams = { setDownloadTypeBottomSheetOn(true);
tid: tid, };
mid: mid,
requestType: requestType, const onSelectDownloadType = (type: 'IMAGE' | 'EMAIL') => {
email: email if (type === 'IMAGE') {
}; // 이미지 저장은 바로 실행
extensionPayoutDetailDownloadCertification(params).then((rs: ExtensionPayoutDetailDownloadCertificateResponse) => { const params: ExtensionPayoutDetailDownloadCertificateParams = {
console.log(rs); 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 {
// 이메일은 EmailBottomSheet 열기
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);
}; };
useEffect(() => { useEffect(() => {
@@ -105,11 +141,11 @@ export const PayoutDetailPage = () => {
</li> </li>
<li className="kv-row"> <li className="kv-row">
<span className="k"></span> <span className="k"></span>
<span className="v">{ detail?.requestDate }</span> <span className="v">{ moment(detail?.requestDate).format('YYYY.MM.DD') }</span>
</li> </li>
<li className="kv-row"> <li className="kv-row">
<span className="k"></span> <span className="k"></span>
<span className="v">{ detail?.settlementDateTime }</span> <span className="v">{moment(detail?.settlementDateTime,'YYYYMMDDHHmmss').format('YYYY.MM.DD HH:mm:ss')}</span>
</li> </li>
<li className="kv-row"> <li className="kv-row">
<span className="k"></span> <span className="k"></span>
@@ -140,6 +176,18 @@ export const PayoutDetailPage = () => {
</div> </div>
</div> </div>
</main> </main>
<DownloadTypeBottomSheet
bottomSheetOn={downloadTypeBottomSheetOn}
setBottomSheetOn={setDownloadTypeBottomSheetOn}
onSelectType={onSelectDownloadType}
/>
<EmailBottomSheet
bottomSheetOn={emailBottomSheetOn}
setBottomSheetOn={setEmailBottomSheetOn}
imageSave={false}
sendEmail={true}
sendRequest={onSendRequest}
/>
</> </>
); );
}; };

View File

@@ -15,9 +15,9 @@ import {
import { JSX, useEffect, useState } from 'react'; import { JSX, useEffect, useState } from 'react';
import { DEFAULT_PAGE_PARAM } from '@/entities/common/model/constant'; import { DEFAULT_PAGE_PARAM } from '@/entities/common/model/constant';
import { import {
useSetHeaderTitle, useSetHeaderTitle,
useSetHeaderType, useSetHeaderType,
useSetFooterMode, useSetFooterMode,
useSetOnBack useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout'; } from '@/widgets/sub-layout/use-sub-layout';
import moment from 'moment'; import moment from 'moment';
@@ -29,8 +29,13 @@ import { ListDateGroup } from '@/entities/additional-service/ui/list-date-group'
import { AdditionalServiceCategory } from '@/entities/additional-service/model/types'; import { AdditionalServiceCategory } from '@/entities/additional-service/model/types';
import { useStore } from '@/shared/model/store'; import { useStore } from '@/shared/model/store';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet'; import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const PayoutListPage = () => { export const PayoutListPage = () => {
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'PAYOUT'
});
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid; const userMid = useStore.getState().UserStore.mid;
@@ -44,12 +49,12 @@ export const PayoutListPage = () => {
const [toDate, setToDate] = useState<string>(moment().format('YYYYMMDD')); const [toDate, setToDate] = useState<string>(moment().format('YYYYMMDD'));
const [status, setStatus] = useState<PayoutDisbursementStatus>(PayoutDisbursementStatus.ALL); const [status, setStatus] = useState<PayoutDisbursementStatus>(PayoutDisbursementStatus.ALL);
const [minAmount, setMinAmount] = useState<number>(0); const [minAmount, setMinAmount] = useState<number>(0);
const [maxAmount, setMaxAmount] = useState<number>(50000000); const [maxAmount, setMaxAmount] = useState<number>(50000000);
const [emailBottomSheetOn, setEmailBottomSheetOn] = useState<boolean>(false); const [emailBottomSheetOn, setEmailBottomSheetOn] = useState<boolean>(false);
const { mutateAsync: extensionPayoutList } = useExtensionPayoutListMutation(); const { mutateAsync: extensionPayoutList } = useExtensionPayoutListMutation();
const { mutateAsync: extensionPayoutExcel } = useExtensionPayoutExcelMutation(); const { mutateAsync: extensionPayoutExcel } = useExtensionPayoutExcelMutation();
useSetHeaderTitle('지급대행'); useSetHeaderTitle('지급대행');
useSetHeaderType(HeaderType.LeftArrow); useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false); useSetFooterMode(false);
@@ -65,26 +70,19 @@ export const PayoutListPage = () => {
sortType?: SortTypeKeys, sortType?: SortTypeKeys,
status?: PayoutDisbursementStatus status?: PayoutDisbursementStatus
}) => { }) => {
let newMinAmount = minAmount;
if(!!minAmount && typeof(minAmount) === 'string'){
newMinAmount = parseInt(minAmount);
}
let newMaxAmount = maxAmount;
if(!!maxAmount && typeof(maxAmount) === 'string'){
newMaxAmount = parseInt(maxAmount);
}
let params: ExtensionPayoutListParams = { let params: ExtensionPayoutListParams = {
mid: mid, mid: mid,
searchDateType: searchDateType, searchDateType: searchDateType,
fromDate: fromDate, fromDate: fromDate,
toDate: toDate, toDate: toDate,
status: option?.status ?? status, status: option?.status ?? status,
minAmount: newMinAmount, minAmount: minAmount,
maxAmount: newMaxAmount, maxAmount: maxAmount,
page: pageParam page: pageParam
}; };
if(params.page){ if (params.page) {
params.page.sortType = option?.sortType || sortType; params.page.sortType = option?.sortType || sortType;
setPageParam(params.page); setPageParam(params.page);
} }
@@ -102,13 +100,9 @@ export const PayoutListPage = () => {
if (selectedEmail) { if (selectedEmail) {
const params: ExtensionPayoutExcelParams = { const params: ExtensionPayoutExcelParams = {
mid: mid, mid: mid,
searchDateType: searchDateType, email: selectedEmail,
fromDate: fromDate, fromDate: fromDate,
toDate: toDate, toDate: toDate,
status: status,
minAmount: minAmount,
maxAmount: maxAmount,
//email: selectedEmail
}; };
extensionPayoutExcel(params).then((rs: ExtensionPayoutExcelResponse) => { extensionPayoutExcel(params).then((rs: ExtensionPayoutExcelResponse) => {
console.log('Excel Download Status:', rs); console.log('Excel Download Status:', rs);
@@ -127,9 +121,6 @@ export const PayoutListPage = () => {
}; };
const onClickToDisbursementStatus = (val: PayoutDisbursementStatus) => { const onClickToDisbursementStatus = (val: PayoutDisbursementStatus) => {
setStatus(val); setStatus(val);
callExtensionPayoutList({
status: val
});
}; };
useEffect(() => { useEffect(() => {
@@ -145,9 +136,9 @@ export const PayoutListPage = () => {
]); ]);
const getListDateGroup = () => { const getListDateGroup = () => {
let rs= []; let rs = [];
let date = ''; let date = '';
let list= []; let list = [];
for (let i = 0; i < listItems.length; i++) { for (let i = 0; i < listItems.length; i++) {
let itemDateStr = ''; let itemDateStr = '';
if (searchDateType === PayoutSearchDateType.REQUEST_DATE) { if (searchDateType === PayoutSearchDateType.REQUEST_DATE) {
@@ -190,6 +181,10 @@ export const PayoutListPage = () => {
return rs; return rs;
}; };
if (!hasAccess) {
return <AccessDeniedDialog />;
}
return ( return (
<> <>
<main> <main>
@@ -198,19 +193,19 @@ export const PayoutListPage = () => {
<section className="summary-section"> <section className="summary-section">
<div className="credit-controls"> <div className="credit-controls">
<div> <div>
<input <input
className="credit-period" className="credit-period"
type="text" type="text"
value={ moment(fromDate).format('YYYY.MM.DD') + '-' + moment(toDate).format('YYYY.MM.DD') } value={moment(fromDate).format('YYYY.MM.DD') + '-' + moment(toDate).format('YYYY.MM.DD')}
readOnly={ true } readOnly={true}
/> />
<button <button
className="filter-btn" className="filter-btn"
aria-label="필터" aria-label="필터"
onClick={ () => onClickToOpenFilter() } onClick={() => onClickToOpenFilter()}
> >
<img <img
src={ IMAGE_ROOT + '/ico_setting.svg' } src={IMAGE_ROOT + '/ico_setting.svg'}
alt="검색옵션" alt="검색옵션"
/> />
</button> </button>
@@ -218,10 +213,10 @@ export const PayoutListPage = () => {
<button <button
className="download-btn" className="download-btn"
aria-label="다운로드" aria-label="다운로드"
onClick={ () => onClickToOpenEmailBottomSheet() } onClick={() => onClickToOpenEmailBottomSheet()}
> >
<img <img
src={ IMAGE_ROOT + '/ico_download.svg' } src={IMAGE_ROOT + '/ico_download.svg'}
alt="다운로드" alt="다운로드"
/> />
</button> </button>
@@ -240,52 +235,52 @@ export const PayoutListPage = () => {
<section className="filter-section"> <section className="filter-section">
<SortTypeBox <SortTypeBox
sortType={ sortType } sortType={sortType}
onClickToSort={ onClickToSort } onClickToSort={onClickToSort}
></SortTypeBox> ></SortTypeBox>
<div className="excrow mr-0"> <div className="excrow mr-0">
<div className="full-menu-keywords no-padding"> <div className="full-menu-keywords no-padding">
{ {
PayoutDisbursementStatusBtnGroup.map((value, index) => ( PayoutDisbursementStatusBtnGroup.map((value, index) => (
<span <span
key={ `key-service-code=${ index }` } key={`key-service-code=${index}`}
className={ `keyword-tag ${(status === value.value)? 'active': ''}` } className={`keyword-tag ${(status === value.value) ? 'active' : ''}`}
onClick={ () => onClickToDisbursementStatus(value.value) } onClick={() => onClickToDisbursementStatus(value.value)}
>{ value.name }</span> >{value.name}</span>
)) ))
} }
</div> </div>
</div> </div>
</section> </section>
<section className="transaction-list"> <section className="transaction-list">
{ getListDateGroup() } {getListDateGroup()}
</section> </section>
<div className="apply-row"> <div className="apply-row">
<button <button
className="btn-50 btn-blue flex-1" className="btn-50 btn-blue flex-1"
onClick={ () => onClickToNavigation() } onClick={() => onClickToNavigation()}
> </button> > </button>
</div> </div>
</div> </div>
</div> </div>
</main> </main>
<PayoutFilter <PayoutFilter
filterOn={ filterOn } filterOn={filterOn}
setFilterOn={ setFilterOn } setFilterOn={setFilterOn}
mid={ mid } mid={mid}
searchDateType={ searchDateType } searchDateType={searchDateType}
fromDate={ fromDate } fromDate={fromDate}
toDate={ toDate } toDate={toDate}
status= { status } status={status}
minAmount={ minAmount } minAmount={minAmount}
maxAmount={ maxAmount } maxAmount={maxAmount}
setMid={ setMid } setMid={setMid}
setSearchDateType={ setSearchDateType } setSearchDateType={setSearchDateType}
setFromDate={ setFromDate } setFromDate={setFromDate}
setToDate={ setToDate } setToDate={setToDate}
setStatus={ setStatus } setStatus={setStatus}
setMinAmount={ setMinAmount } setMinAmount={setMinAmount}
setMaxAmount={ setMaxAmount } setMaxAmount={setMaxAmount}
></PayoutFilter> ></PayoutFilter>
<EmailBottomSheet <EmailBottomSheet
bottomSheetOn={emailBottomSheetOn} bottomSheetOn={emailBottomSheetOn}

View File

@@ -8,12 +8,14 @@ import {
useSetFooterMode, useSetFooterMode,
useSetOnBack useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout'; } from '@/widgets/sub-layout/use-sub-layout';
import { useState } from "react"; import { ChangeEvent, useState } from "react";
import { useExtensionPayoutRequestMutation } from "@/entities/additional-service/api/payout/use-extension-payout-request-mutation"; import { useExtensionPayoutRequestMutation } from "@/entities/additional-service/api/payout/use-extension-payout-request-mutation";
import { ExtensionPayoutRequestParams, ExtensionPayoutRequestResponse } from "@/entities/additional-service/model/payout/types"; import { ExtensionPayoutRequestParams, ExtensionPayoutRequestResponse } from "@/entities/additional-service/model/payout/types";
import NiceCalendar from "@/shared/ui/calendar/nice-calendar"; import NiceCalendar from "@/shared/ui/calendar/nice-calendar";
import { useStore } from "@/shared/model/store"; import { useStore } from "@/shared/model/store";
import moment from 'moment'; import moment from 'moment';
import { NumericFormat } from "react-number-format";
import { snackBar } from "@/shared/lib";
export const PayoutRequestPage = () => { export const PayoutRequestPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
@@ -41,9 +43,14 @@ export const PayoutRequestPage = () => {
disbursementAmount: disbursementAmount, disbursementAmount: disbursementAmount,
settlementDate: settlementDate, settlementDate: settlementDate,
}; };
extensionPayoutRequest(params).then((rs: ExtensionPayoutRequestResponse) => { extensionPayoutRequest(params)
navigate(PATHS.additionalService.payout.list); .then((rs) => {
}); snackBar("신청을 성공하였습니다.")
})
.catch((error) => {
snackBar(`[실패] ${error?.response?.data?.message} `|| '[실패] 신청을 실패하였습니다.')
})
;
}; };
const isFormValid = () => { const isFormValid = () => {
@@ -58,9 +65,8 @@ export const PayoutRequestPage = () => {
setSettlementDate(moment(date).format('YYYYMMDD')); setSettlementDate(moment(date).format('YYYYMMDD'));
setCalendarOpen(false); setCalendarOpen(false);
}; };
const onClickToOpenCalendar = () => {
setCalendarOpen(true);
};
return ( return (
<> <>
<main> <main>
@@ -69,44 +75,44 @@ export const PayoutRequestPage = () => {
<div className="ing-list"> <div className="ing-list">
<div className="billing-form gap-30"> <div className="billing-form gap-30">
<div className="billing-row"> <div className="billing-row">
<div className="billing-label">ID<span>*</span></div> <div className="billing-label">ID</div>
<div className="billing-field"> <div className="billing-field">
<input <input
type="text" type="text"
value={submallId} value={submallId}
onChange={(e) => setSubmallId(e.target.value)} onChange={(e: ChangeEvent<HTMLInputElement>) => setSubmallId(e.target.value)}
/> />
</div> </div>
</div> </div>
<div className="billing-row"> <div className="billing-row">
<div className="billing-label"><span>*</span></div> <div className="billing-label"></div>
<div className="billing-field"> <div className="billing-field">
<input <NumericFormat
type="text"
value={disbursementAmount} value={disbursementAmount}
onChange={(e) => setDisbursementAmount(parseInt(e.target.value))} allowNegative={false}
/> displayType="input"
onChange={(e: ChangeEvent<HTMLInputElement>) => setDisbursementAmount(parseInt(e.target.value))}
></NumericFormat>
</div> </div>
</div> </div>
<div className="billing-row"> <div className="billing-row">
<div className="billing-label"><span>*</span></div> <div className="billing-label"></div>
<div className="billing-field"> <div className="billing-field">
<div className="input-wrapper date"> <div className="input-wrapper date wid-100">
<input <input
className="date-input"
type="text" type="text"
placeholder="날짜 선택" placeholder="날짜 선택"
value={settlementDate} value={settlementDate ? moment(settlementDate).format('YYYY.MM.DD') : '' }
readOnly={true}
/> />
<button <button
className="date-btn" className="date-btn"
type="button" type="button"
onClick={() => onClickToOpenCalendar()} onClick={() => setCalendarOpen(true)}
disabled={!isFormValid}
> >
<img <img
src={IMAGE_ROOT + '/ico_date.svg'} src={IMAGE_ROOT + '/ico_date.svg'}
alt="날짜 선택" alt="clear"
/> />
</button> </button>
</div> </div>
@@ -129,6 +135,7 @@ export const PayoutRequestPage = () => {
setCalendarOpen={setCalendarOpen} setCalendarOpen={setCalendarOpen}
calendarType={CalendarType.Single} calendarType={CalendarType.Single}
setNewDate={setNewDate} setNewDate={setNewDate}
minDate={new Date()}
></NiceCalendar> ></NiceCalendar>
</> </>
); );

View File

@@ -21,12 +21,17 @@ import { useStore } from '@/shared/model/store';
import { AdditionalServiceCategory } from '@/entities/additional-service/model/types'; import { AdditionalServiceCategory } from '@/entities/additional-service/model/types';
import { PATHS } from '@/shared/constants/paths'; import { PATHS } from '@/shared/constants/paths';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet'; import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const SmsPaymentPage = () => { export const SmsPaymentPage = () => {
const { navigate } = useNavigate(); const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid; const userMid = useStore.getState().UserStore.mid;
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'SMS'
});
const [bottomSmsPaymentDetailResendOn, setBottomSmsPaymentDetailResendOn] = useState<boolean>(false) const [bottomSmsPaymentDetailResendOn, setBottomSmsPaymentDetailResendOn] = useState<boolean>(false)
const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST); const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST);
@@ -131,6 +136,10 @@ export const SmsPaymentPage = () => {
smsCl smsCl
]); ]);
// if (!hasAccess) {
// return <AccessDeniedDialog />;
// }
return ( return (
<> <>
<main> <main>

View File

@@ -11,7 +11,7 @@ export const API_URL_ADDITIONAL_SERVICE = {
}, },
extensionAccountHolderAuthDownlaodExcel: () => { extensionAccountHolderAuthDownlaodExcel: () => {
// POST: 계좌점유인증 엑셀 다운 // POST: 계좌점유인증 엑셀 다운
return `${API_BASE_URL}/api/v1/${API_URL_KEY}/extension/account-auth/excel`; return `${API_BASE_URL}/api/v1/${API_URL_KEY}/extension/account-auth/download/excel`;
}, },
extensionAccountHolderAuthDetail: () => { extensionAccountHolderAuthDetail: () => {
// POST: 계좌점유인증 상세 조회 // POST: 계좌점유인증 상세 조회
@@ -97,6 +97,10 @@ export const API_URL_ADDITIONAL_SERVICE = {
// POST: 부가서비스 조회 // POST: 부가서비스 조회
return `${API_BASE_URL}/api/v1/${API_URL_KEY}/extension/list`; return `${API_BASE_URL}/api/v1/${API_URL_KEY}/extension/list`;
}, },
extensionCheck: () => {
// POST: 부가서비스 사용여부 체크
return `${API_BASE_URL}/api/v1/${API_URL_KEY}/extension/check`;
},
extensionKeyinList: () => { extensionKeyinList: () => {
// POST: KEY-IN 결제 목록 조회 // POST: KEY-IN 결제 목록 조회
return `${API_BASE_URL}/api/v1/${API_URL_KEY}/extension/keyin/list`; return `${API_BASE_URL}/api/v1/${API_URL_KEY}/extension/keyin/list`;

View File

@@ -0,0 +1,97 @@
import { useEffect, useState } from 'react';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { useExtensionCheckMutation } from '@/entities/additional-service/api/use-extension-check-mutation';
import { Dialog } from '@/shared/ui/dialogs/dialog';
import { PATHS } from '@/shared/constants/paths';
interface UseExtensionAccessCheckProps {
extensionCode: string;
enabled?: boolean;
}
interface UseExtensionAccessCheckReturn {
hasAccess: boolean | null;
AccessDeniedDialog: () => React.ReactElement | null;
}
/**
* 부가서비스 접근 권한을 체크하는 훅
* 페이지 마운트 시 extensionCheck API를 호출하여 권한을 확인하고,
* 권한이 없으면 다이얼로그를 표시한 후 이전 페이지로 이동합니다.
*/
export const useExtensionAccessCheck = ({
extensionCode,
enabled = true
}: UseExtensionAccessCheckProps): UseExtensionAccessCheckReturn => {
const { navigate } = useNavigate();
const { mutateAsync: extensionCheck } = useExtensionCheckMutation();
const [hasAccess, setHasAccess] = useState<boolean | null>(null);
const [isChecking, setIsChecking] = useState<boolean>(true);
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
useEffect(() => {
if (!enabled) {
setIsChecking(false);
setHasAccess(true);
return;
}
const checkAccess = async () => {
try {
setIsChecking(true);
const result = await extensionCheck({ extensionCode });
if (result.checkResult === true) {
// 권한 체크 통과
setHasAccess(true);
} else {
// 권한 체크 실패 - 다이얼로그 표시
setHasAccess(false);
setDialogOpen(true);
}
} catch (error) {
console.error('Extension access check error:', error);
// 에러 발생 시에도 다이얼로그 표시
setHasAccess(false);
setDialogOpen(true);
} finally {
setIsChecking(false);
}
};
checkAccess();
}, [extensionCode, enabled]);
const handleConfirm = () => {
setDialogOpen(false);
setTimeout(() => {
navigate(PATHS.additionalService.list);
}, 0);
};
const AccessDeniedDialog = () => {
return (
<Dialog
open={dialogOpen}
onClose={() => {}}
message={
<>
.<br />
.
</>
}
buttonLabel={['확인']}
onConfirmClick={handleConfirm}
afterLeave={() => {}}
/>
);
};
return {
hasAccess,
AccessDeniedDialog,
};
};

View File

@@ -36,7 +36,7 @@ export const Dialog = ({
const { nativeDialog, resetNativeDialog } = useBridge(bridge.store, (state) => state); const { nativeDialog, resetNativeDialog } = useBridge(bridge.store, (state) => state);
const whenToBlock = useCallback(() => open, [open]); const whenToBlock = useCallback(() => open, [open]);
// 다이얼로그가 열려있을때 뒤로가기 방지
const blocker = useBlocker(whenToBlock); const blocker = useBlocker(whenToBlock);
const nativeMessage = nativeDialog?.message; const nativeMessage = nativeDialog?.message;
const displayMessage = nativeMessage || message; const displayMessage = nativeMessage || message;
@@ -68,7 +68,6 @@ export const Dialog = ({
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<_Dialog onClose={shouldCloseOnBackdropClick ? onClose : () => null} static={!shouldCloseOnBackdropClick}> <_Dialog onClose={shouldCloseOnBackdropClick ? onClose : () => null} static={!shouldCloseOnBackdropClick}>
{/* The backdrop, rendered as a fixed sibling to the panel container */}
<div <div
id="cancelConfirmPopup" id="cancelConfirmPopup"
className="popup-overlay" className="popup-overlay"