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 '';
const serviceCodeMap: Record<string, string> = {
'01': '카드',
'02': '신용카드',
'03': '가상계좌',
'05': '휴대폰'
'CARD': '신용카드',
'BANK': '계좌이체',
'VBANK': '가상계좌',
'PHONE': '휴대폰'
}
return serviceCodeMap[status] || status;

View File

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

View File

@@ -9,4 +9,16 @@ export const PayoutDisbursementStatusBtnGroup = [
{name: '요청', value: PayoutDisbursementStatus.REQUEST},
{name: '성공', value: PayoutDisbursementStatus.SUCCESS},
{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;
settlementDate: string;
};
export interface ExtensionPayoutRequestResponse {};
export interface ExtensionPayoutRequestResponse {
status: boolean;
};
export enum PayoutSearchDateType {
REQUEST_DATE = 'REQUEST_DATE',
SETTLEMENT_DATE = 'SETTLEMENT_DATE',
@@ -36,10 +38,15 @@ export interface PayoutContent {
requestDate?: string;
settlementDate?: string;
companyName?: string;
disbursementStatus?: PayoutDisbursementStatus;
disbursementAmount?: number;
status?: PayoutDisbursementStatus;
amount?: number;
};
export interface ExtensionPayoutExcelParams {
mid: string;
email: string;
fromDate: string;
toDate: string;
};
export interface ExtensionPayoutExcelParams extends ExtensionPayoutListParams {};
export interface ExtensionPayoutExcelResponse {}

View File

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

View File

@@ -10,6 +10,8 @@ import { AccountHolderSearchListItem } from './account-holder-search/types';
import { KeyInPaymentListItem } from './key-in/types';
import { AccountHolderAuthListItem, AccountHolderAuthStatus } from './account-holder-auth/types';
import { LinkContentType, LinkPaymentHistoryListItem, LinkPaymentSendMethod, LinkPaymentWaitListItem } from './link-pay/types';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { PATHS } from '@/shared/constants/paths';
// ========================================
// 공통 Enums 및 타입들
@@ -47,6 +49,20 @@ export enum Language {
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
// ========================================
@@ -223,8 +239,17 @@ export interface ExtensionListItemProps {
availableExtensionList: Array<availableExtensionListItem>;
}
export interface ExtensionListResponse extends DefaulResponsePagination {
content: Array<ExtensionListItemProps>
export interface ExtensionListResponse {
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 { IMAGE_ROOT } from '@/shared/constants/common';
import { motion } from 'framer-motion';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { FilterSelect } from '@/shared/ui/filter/select';
import { FilterCalendar } from '@/shared/ui/filter/calendar';
import { FilterButtonGroups } from '@/shared/ui/filter/button-groups';
@@ -42,6 +42,9 @@ export const AccountHolderAuthFilter = ({
onClickToClose();
};
useEffect(() => {
setFilterAuthStatus(authStatus);
}, [authStatus]);
return (
<>
<motion.div

View File

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

View File

@@ -12,30 +12,31 @@ import { getArsPaymentStatusName, getArsOrderStatusName } from '../model/ars/con
import { ServiceCode } from '../model/alimtalk/types';
import { getAlimtalkAlimClText, getAlimtalkSendClTypeText, getAlimtalkSendTypeText, getAlimtalkServiceCodeText } from '../model/alimtalk/constant';
import { getAuthResultStatusText, getTransTypeText } from '../model/face-auth/constant';
import { getPayoutStatusText } from '../model/payout/constant';
export const ListItem = ({
additionalServiceCategory,
mid, tid, paymentDate, paymentStatus,
applicationDate, requestDate, bankName, accountNo, resultStatus, resultMessage,
amount, sendMethod, processStatus,registDate,
amount, sendMethod, processStatus, registDate,
accountName,
submallId, settlementDate, companyName,
disbursementStatus, disbursementAmount,
status: disbursementStatus, amount: disbursementAmount,
orderStatus, arsPaymentMethod,
alimCl, sendType, sendCl,
paymentMethod, receiverName,
requestId,subReqId,
buyerName,receiverInfo,
seq,serviceCode,sendDate,
authStatus,
requestId, subReqId,
buyerName, receiverInfo,
seq, serviceCode, sendDate,
authStatus, status,
smsCl,groupId,userMallId,transType,
authResult,failReason,requestTime,
smsCl, groupId, userMallId, transType,
authResult, failReason, requestTime,
onResendClick
}: ListItemProps) => {
const { navigate } = useNavigate();
@@ -89,7 +90,7 @@ export const ListItem = ({
}
}
else if (additionalServiceCategory === AdditionalServiceCategory.Alimtalk) {
if (sendCl === "SUCCESS" || "REQUEST") {
if (sendCl === "SUCCESS" || sendCl === "REQUEST") {
rs = 'blue';
} else {
rs = 'gray';
@@ -104,20 +105,13 @@ export const ListItem = ({
}
}
else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentHistory) {
if (paymentStatus === "PAYMENT_COMPLETE") {
if (paymentStatus === "0" || paymentStatus === "1" || paymentStatus === "2") {
rs = 'blue';
}
else if (paymentStatus === "ACTIVE") {
rs = 'blue';
else if (paymentStatus === "3") {
rs = 'gray';
}
else if (paymentStatus === "DEPOSIT_REQUEST") {
rs = 'blue';
}
else if (paymentStatus === "PAYMENT_FAIL") {
rs = 'blue';
}
else if (paymentStatus === "INACTIVE") {
else if (paymentStatus === '4') {
rs = 'gray';
}
} else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentWait) {
@@ -144,6 +138,12 @@ export const ListItem = ({
} else if (resultStatus === "FAIL") {
rs = 'gray';
}
} else if (additionalServiceCategory === AdditionalServiceCategory.Payout) {
if (status === "SUCCESS" || status === "REQUEST") {
rs = 'blue';
} else if (status === "FAIL") {
rs = 'gray';
}
}
return rs;
@@ -286,7 +286,7 @@ export const ListItem = ({
if (applicationDate && applicationDate.length >= 12) {
timeStr = applicationDate.substring(8, 10) + ':' + applicationDate.substring(10, 12);
} else {
timeStr = requestDate?.substring(8,10) + ':' + requestDate?.substring(10, 12);
timeStr = requestDate?.substring(8, 10) + ':' + requestDate?.substring(10, 12);
}
}
else if (additionalServiceCategory === AdditionalServiceCategory.Ars) {
@@ -349,9 +349,10 @@ export const ListItem = ({
else if (additionalServiceCategory === AdditionalServiceCategory.FaceAuth) {
str = `${userMallId}(${mid})`;
}
else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentHistory ||
additionalServiceCategory === AdditionalServiceCategory.LinkPaymentWait
else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentHistory
) {
str = `${buyerName}`;
} else if (additionalServiceCategory === AdditionalServiceCategory.LinkPaymentWait) {
str = `${buyerName}(${receiverInfo})`;
}
else if (additionalServiceCategory === AdditionalServiceCategory.Payout) {
@@ -451,7 +452,7 @@ export const ListItem = ({
else if (additionalServiceCategory === AdditionalServiceCategory.Payout) {
rs.push(
<div className="transaction-details">
<span>{disbursementStatus}</span>
<span>{getPayoutStatusText(disbursementStatus)}</span>
<span className="separator">|</span>
<span>{submallId}</span>
</div>
@@ -498,7 +499,7 @@ export const ListItem = ({
);
}
else if (additionalServiceCategory === AdditionalServiceCategory.Alimtalk) {
console.log(serviceCode)
console.log(serviceCode)
rs.push(
<div className="transaction-details">
<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 { AdditionalServiceCategory } from '@/entities/additional-service/model/types';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const AccountHolderAuthPage = () => {
const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid;
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'ACCOUNT_AUTH'
});
const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST);
const [listItems, setListItems] = useState<Array<ExtensionAccountHolderAuthContentItem>>([]);
const [filterOn, setFilterOn] = useState<boolean>(false);
@@ -115,6 +121,10 @@ export const AccountHolderAuthPage = () => {
authStatus
]);
if (!hasAccess) {
return <AccessDeniedDialog />;
}
return (
<>
<main>

View File

@@ -83,7 +83,7 @@ export const AccountHolderAuthDetailPage = () => {
</li>
<li className="kv-row">
<span className="k"></span>
<span className="v">{detail?.bankName}</span>
<span className="v">{detail?.accountNo}</span>
</li>
<li className="kv-row">
<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 { resultStatusBtnGroup } from '@/entities/additional-service/model/account-holder-search/constant';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const AccountHolderSearchPage = () => {
const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid;
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'SEARCH_ACCOUNT_NAME'
});
const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST);
const [listItems, setListItems] = useState<Array<AccountHolderSearchListItem>>([]);
const [pageParam, setPageParam] = useState<DefaultRequestPagination>(DEFAULT_PAGE_PARAM);
@@ -125,6 +131,10 @@ export const AccountHolderSearchPage = () => {
resultStatus
]);
if (!hasAccess) {
return <AccessDeniedDialog />;
}
return (
<>
<main>

View File

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

View File

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

View File

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

View File

@@ -165,9 +165,9 @@ export const AlimtalkSettingPage = () => {
></AlimTalkSettingServiceRow>
<AlimTalkSettingServiceRow
title='신용카드(취소)'
merchantFlag={merchantBankApprovalFlag}
merchantFlag={merchantCardCancelFlag}
userFlag={userCardCancelFlag}
setMerchantFlag={setMerchantBankApprovalFlag}
setMerchantFlag={setMerchantCardCancelFlag}
setUserFlag={setUserCardCancelFlag}
></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 { useStore } from '@/shared/model/store';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const ArsListPage = () => {
const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid;
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'ARS'
});
const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST);
const [listItems, setListItems] = useState<Array<ArsListContent>>([]);
const [filterOn, setFilterOn] = useState<boolean>(false);
@@ -119,9 +125,6 @@ export const ArsListPage = () => {
setPaymentStatus(val);
};
useEffect(() => {
callList();
}, []);
useEffect(() => {
callList();
}, [
@@ -181,6 +184,10 @@ export const ArsListPage = () => {
return rs;
}
// if (!hasAccess) {
// return <AccessDeniedDialog />;
// }
return (
<>
<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 { 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 { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const FaceAuthPage = () => {
const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid;
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'FACE_AUTH'
});
const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST);
const [listItems, setListItems] = useState<Array<FaceAuthListItem>>([]);
const [filterOn, setFilterOn] = useState<boolean>(false);
@@ -54,7 +60,7 @@ export const FaceAuthPage = () => {
authResult?: FaceAuthResult
}) => {
let params: ExtensionFaceAuthListParams = {
mid: mid,
mid: "faceauth0m",
userMallId: userMallId,
fromDate: fromDate,
toDate: toDate,
@@ -80,7 +86,7 @@ export const FaceAuthPage = () => {
const onSendRequest = (selectedEmail?: string) => {
if (selectedEmail) {
const params: ExtensionFaceAuthExcelDownlaodPrams = {
mid: mid,
mid: "faceauth0m",
email: selectedEmail,
fromDate: fromDate,
toDate: toDate
@@ -106,9 +112,9 @@ export const FaceAuthPage = () => {
};
const getListDateGroup = () => {
let rs= [];
let rs = [];
let date = '';
let list= [];
let list = [];
for (let i = 0; i < listItems.length; i++) {
let item = listItems[i];
if (!!item) {
@@ -155,6 +161,10 @@ export const FaceAuthPage = () => {
callList();
}, [mid, userMallId, fromDate, toDate, transType, authResult]);
// if (!hasAccess) {
// return <AccessDeniedDialog />;
// }
return (
<>
<main>

View File

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

View File

@@ -138,7 +138,7 @@ export const FundAccountTransferRequestPage = () => {
<input
type="text"
value={accountName}
onChange={(e) => setAccountName(e.target.value)}
onChange={(e: ChangeEvent<HTMLInputElement>) => setAccountName(e.target.value) }
/>
</div>
</div>
@@ -159,7 +159,7 @@ export const FundAccountTransferRequestPage = () => {
<input
type="text"
value={moid}
onChange={(e) => setMoid(e.target.value)}
onChange={(e: ChangeEvent<HTMLInputElement>) => setMoid(e.target.value)}
/>
</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 { keyInPaymentPaymentStatusBtnGroup } from '@/entities/additional-service/model/key-in/constant';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const KeyInPaymentPage = () => {
const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid;
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'KEYIN'
});
const [sortType, setSortType] = useState<SortTypeKeys>(SortTypeKeys.LATEST);
const [listItems, setListItems] = useState<Array<KeyInPaymentListItem>>([]);
const [filterOn, setFilterOn] = useState<boolean>(false);
@@ -136,6 +142,11 @@ export const KeyInPaymentPage = () => {
maxAmount
]);
// if (!hasAccess) {
// return <AccessDeniedDialog />;
// }
return (
<>
<main>

View File

@@ -12,6 +12,7 @@ import {
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
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 = () => {
const { navigate } = useNavigate();
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'LINKPAY'
});
const [activeTab, setActiveTab] = useState<LinkPaymentTabKeys>(LinkPaymentTabKeys.ShippingHistory)
useSetHeaderTitle('링크결제')
@@ -29,6 +35,10 @@ export const LinkPaymentHistoryPage = () => {
navigate(PATHS.home);
});
if (!hasAccess) {
return <AccessDeniedDialog />;
}
return (
<>
<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 { 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 { 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 = () => {
const { navigate } = useNavigate();
@@ -54,13 +55,13 @@ export const LinkPaymentWaitDetailPage = () => {
requestId: requestId
}
linkPayWaitDelete(deleteParam)
.then((response) => {
console.log("Delete 성공 응답: ", response)
.then((rs) => {
onClickToNavigate(PATHS.additionalService.linkPayment.pendingSend)
snackBar("삭제를 성공하였습니다.")
})
.catch((error) => {
console.error("Resend 실패: ", error);
snackBar(`[실패] ${error?.response?.data?.message}`)
});
}
@@ -120,6 +121,7 @@ export const LinkPaymentWaitDetailPage = () => {
<button
className="btn-50 btn-blue flex-1"
onClick={() => onClickToCancel()}
disabled={paymentInfo?.processStatus !== LinkPaymentProcessStatus.SEND_REQUEST}
></button>
</div>
</div>

View File

@@ -2,16 +2,16 @@ import { ChangeEvent, useEffect, useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { HeaderType } from '@/entities/common/model/types';
import { IMAGE_ROOT } from '@/shared/constants/common';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
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 { Dialog } from '@/shared/ui/dialogs/dialog';
export const ListPage = () => {
const { navigate } = useNavigate();
@@ -19,6 +19,9 @@ export const ListPage = () => {
const userMid = useStore.getState().UserStore.mid;
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();
@@ -34,112 +37,63 @@ export const ListPage = () => {
mid: mid
}
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 = () => {
let rs = [];
for(let i=0;i<activeExtensionList.length;i++){
rs.push(
<div
key={ 'key-active-' + i }
className={ activeExtensionList[i]?.className }
onClick={ () => onClickToNavigate(activeExtensionList[i]?.path) }
>
<div>
<div className="service-name">{ activeExtensionList[i]?.serviceName }</div>
<p className="service-desc">{ activeExtensionList[i]?.serviceDesc }</p>
</div>
<img
src={ activeExtensionList[i]?.icon }
alt={ activeExtensionList[i]?.serviceName }
/>
const filteredServices = SERVICE_MAP.filter(service =>
activeServices.includes(service.code)
);
return filteredServices.map((service) => (
<div
key={'key-active-' + service.code}
className="list-wrap01"
onClick={() => service.path && navigate(service.path)}
>
<div>
<div className="service-name">{service.serviceName}</div>
<p className="service-desc">{service.serviceDesc}</p>
</div>
);
}
return rs;
<img
src={service.icon}
alt={service.serviceName}
/>
</div>
));
};
const getAvailableExtensionList = () => {
let rs = [];
for(let i=0;i<availableExtensionList.length;i++){
rs.push(
<div
key={ 'key-available-' + i }
className={ availableExtensionList[i]?.className }
onClick={ () => onClickToNavigate(availableExtensionList[i]?.path) }
>
<div>
<div className="service-name">{ availableExtensionList[i]?.serviceName }</div>
<p className="service-desc">{ availableExtensionList[i]?.serviceDesc }</p>
</div>
<img
src={ availableExtensionList[i]?.icon }
alt={ availableExtensionList[i]?.serviceName }
/>
const filteredServices = SERVICE_MAP.filter(service =>
availableServices.includes(service.code)
);
return filteredServices.map((service) => (
<div
key={'key-available-' + service.code}
className="list-wrap02"
onClick={() => setDialogOpen(true)}
>
<div>
<div className="service-name">{service.serviceName}</div>
<p className="service-desc">{service.serviceDesc}</p>
</div>
);
}
return rs;
<img
src={service.icon}
alt={service.serviceName}
/>
</div>
));
};
useEffect(() => {
if(!!mid){
if (!!mid) {
callExtensionList();
}
}, [mid]);
return (
<>
<main>
@@ -147,28 +101,41 @@ export const ListPage = () => {
<div className="tab-pane sub active">
<div className="ing-list">
<div className="input-wrapper top-select">
<select
value={ mid }
onChange={ (e: ChangeEvent<HTMLSelectElement>) => setMid(e.target.value) }
>
{
midOptions.map((value, index) => (
<option
key={ value.value }
value={ value.value }
>{ value.name }</option>
))
}
</select>
<select
value={mid}
onChange={(e: ChangeEvent<HTMLSelectElement>) => setMid(e.target.value)}
>
{
midOptions.map((value) => (
<option
key={value.value}
value={value.value}
>{value.name}</option>
))
}
</select>
</div>
<h3 className="ing-title"> </h3>
{ getActiveExtensionList() }
{getActiveExtensionList()}
<h3 className="ing-title"> </h3>
{ getAvailableExtensionList() }
{getAvailableExtensionList()}
</div>
</div>
</div>
</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 { NumericFormat } from 'react-number-format';
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 = () => {
const { navigate } = useNavigate();
@@ -21,9 +24,9 @@ export const PayoutDetailPage = () => {
const tid = location.state.tid;
const mid = location.state.mid;
const [requestType, setRequestType] = useState<string>('');
const [email, setEmail] = useState<string>('');
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();
@@ -47,15 +50,48 @@ export const PayoutDetailPage = () => {
});
const onClickToDownload = () => {
let params: ExtensionPayoutDetailDownloadCertificateParams = {
tid: tid,
mid: mid,
requestType: requestType,
email: email
};
extensionPayoutDetailDownloadCertification(params).then((rs: ExtensionPayoutDetailDownloadCertificateResponse) => {
console.log(rs);
});
setDownloadTypeBottomSheetOn(true);
};
const onSelectDownloadType = (type: 'IMAGE' | 'EMAIL') => {
if (type === 'IMAGE') {
// 이미지 저장은 바로 실행
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 {
// 이메일은 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(() => {
@@ -105,11 +141,11 @@ export const PayoutDetailPage = () => {
</li>
<li className="kv-row">
<span className="k"></span>
<span className="v">{ detail?.requestDate }</span>
<span className="v">{ moment(detail?.requestDate).format('YYYY.MM.DD') }</span>
</li>
<li className="kv-row">
<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 className="kv-row">
<span className="k"></span>
@@ -140,6 +176,18 @@ export const PayoutDetailPage = () => {
</div>
</div>
</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 { DEFAULT_PAGE_PARAM } from '@/entities/common/model/constant';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
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 { useStore } from '@/shared/model/store';
import { EmailBottomSheet } from '@/entities/common/ui/email-bottom-sheet';
import { useExtensionAccessCheck } from '@/shared/lib/hooks/use-extension-access-check';
export const PayoutListPage = () => {
// 권한 체크
const { hasAccess, AccessDeniedDialog } = useExtensionAccessCheck({
extensionCode: 'PAYOUT'
});
const { navigate } = useNavigate();
const userMid = useStore.getState().UserStore.mid;
@@ -44,12 +49,12 @@ export const PayoutListPage = () => {
const [toDate, setToDate] = useState<string>(moment().format('YYYYMMDD'));
const [status, setStatus] = useState<PayoutDisbursementStatus>(PayoutDisbursementStatus.ALL);
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 { mutateAsync: extensionPayoutList } = useExtensionPayoutListMutation();
const { mutateAsync: extensionPayoutExcel } = useExtensionPayoutExcelMutation();
useSetHeaderTitle('지급대행');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false);
@@ -65,26 +70,19 @@ export const PayoutListPage = () => {
sortType?: SortTypeKeys,
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 = {
mid: mid,
searchDateType: searchDateType,
fromDate: fromDate,
toDate: toDate,
status: option?.status ?? status,
minAmount: newMinAmount,
maxAmount: newMaxAmount,
minAmount: minAmount,
maxAmount: maxAmount,
page: pageParam
};
if(params.page){
if (params.page) {
params.page.sortType = option?.sortType || sortType;
setPageParam(params.page);
}
@@ -102,13 +100,9 @@ export const PayoutListPage = () => {
if (selectedEmail) {
const params: ExtensionPayoutExcelParams = {
mid: mid,
searchDateType: searchDateType,
email: selectedEmail,
fromDate: fromDate,
toDate: toDate,
status: status,
minAmount: minAmount,
maxAmount: maxAmount,
//email: selectedEmail
};
extensionPayoutExcel(params).then((rs: ExtensionPayoutExcelResponse) => {
console.log('Excel Download Status:', rs);
@@ -127,9 +121,6 @@ export const PayoutListPage = () => {
};
const onClickToDisbursementStatus = (val: PayoutDisbursementStatus) => {
setStatus(val);
callExtensionPayoutList({
status: val
});
};
useEffect(() => {
@@ -145,9 +136,9 @@ export const PayoutListPage = () => {
]);
const getListDateGroup = () => {
let rs= [];
let rs = [];
let date = '';
let list= [];
let list = [];
for (let i = 0; i < listItems.length; i++) {
let itemDateStr = '';
if (searchDateType === PayoutSearchDateType.REQUEST_DATE) {
@@ -190,6 +181,10 @@ export const PayoutListPage = () => {
return rs;
};
if (!hasAccess) {
return <AccessDeniedDialog />;
}
return (
<>
<main>
@@ -198,19 +193,19 @@ export const PayoutListPage = () => {
<section className="summary-section">
<div className="credit-controls">
<div>
<input
<input
className="credit-period"
type="text"
value={ moment(fromDate).format('YYYY.MM.DD') + '-' + moment(toDate).format('YYYY.MM.DD') }
readOnly={ true }
value={moment(fromDate).format('YYYY.MM.DD') + '-' + moment(toDate).format('YYYY.MM.DD')}
readOnly={true}
/>
<button
className="filter-btn"
<button
className="filter-btn"
aria-label="필터"
onClick={ () => onClickToOpenFilter() }
onClick={() => onClickToOpenFilter()}
>
<img
src={ IMAGE_ROOT + '/ico_setting.svg' }
<img
src={IMAGE_ROOT + '/ico_setting.svg'}
alt="검색옵션"
/>
</button>
@@ -218,10 +213,10 @@ export const PayoutListPage = () => {
<button
className="download-btn"
aria-label="다운로드"
onClick={ () => onClickToOpenEmailBottomSheet() }
onClick={() => onClickToOpenEmailBottomSheet()}
>
<img
src={ IMAGE_ROOT + '/ico_download.svg' }
src={IMAGE_ROOT + '/ico_download.svg'}
alt="다운로드"
/>
</button>
@@ -240,52 +235,52 @@ export const PayoutListPage = () => {
<section className="filter-section">
<SortTypeBox
sortType={ sortType }
onClickToSort={ onClickToSort }
sortType={sortType}
onClickToSort={onClickToSort}
></SortTypeBox>
<div className="excrow mr-0">
<div className="full-menu-keywords no-padding">
{
PayoutDisbursementStatusBtnGroup.map((value, index) => (
<span
key={ `key-service-code=${ index }` }
className={ `keyword-tag ${(status === value.value)? 'active': ''}` }
onClick={ () => onClickToDisbursementStatus(value.value) }
>{ value.name }</span>
))
}
{
PayoutDisbursementStatusBtnGroup.map((value, index) => (
<span
key={`key-service-code=${index}`}
className={`keyword-tag ${(status === value.value) ? 'active' : ''}`}
onClick={() => onClickToDisbursementStatus(value.value)}
>{value.name}</span>
))
}
</div>
</div>
</section>
<section className="transaction-list">
{ getListDateGroup() }
{getListDateGroup()}
</section>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={ () => onClickToNavigation() }
onClick={() => onClickToNavigation()}
> </button>
</div>
</div>
</div>
</main>
<PayoutFilter
filterOn={ filterOn }
setFilterOn={ setFilterOn }
mid={ mid }
searchDateType={ searchDateType }
fromDate={ fromDate }
toDate={ toDate }
status= { status }
minAmount={ minAmount }
maxAmount={ maxAmount }
setMid={ setMid }
setSearchDateType={ setSearchDateType }
setFromDate={ setFromDate }
setToDate={ setToDate }
setStatus={ setStatus }
setMinAmount={ setMinAmount }
setMaxAmount={ setMaxAmount }
filterOn={filterOn}
setFilterOn={setFilterOn}
mid={mid}
searchDateType={searchDateType}
fromDate={fromDate}
toDate={toDate}
status={status}
minAmount={minAmount}
maxAmount={maxAmount}
setMid={setMid}
setSearchDateType={setSearchDateType}
setFromDate={setFromDate}
setToDate={setToDate}
setStatus={setStatus}
setMinAmount={setMinAmount}
setMaxAmount={setMaxAmount}
></PayoutFilter>
<EmailBottomSheet
bottomSheetOn={emailBottomSheetOn}

View File

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

View File

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

View File

@@ -11,7 +11,7 @@ export const API_URL_ADDITIONAL_SERVICE = {
},
extensionAccountHolderAuthDownlaodExcel: () => {
// 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: () => {
// POST: 계좌점유인증 상세 조회
@@ -97,6 +97,10 @@ export const API_URL_ADDITIONAL_SERVICE = {
// POST: 부가서비스 조회
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: () => {
// POST: KEY-IN 결제 목록 조회
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 whenToBlock = useCallback(() => open, [open]);
// 다이얼로그가 열려있을때 뒤로가기 방지
const blocker = useBlocker(whenToBlock);
const nativeMessage = nativeDialog?.message;
const displayMessage = nativeMessage || message;
@@ -68,7 +68,6 @@ export const Dialog = ({
leaveTo="opacity-0"
>
<_Dialog onClose={shouldCloseOnBackdropClick ? onClose : () => null} static={!shouldCloseOnBackdropClick}>
{/* The backdrop, rendered as a fixed sibling to the panel container */}
<div
id="cancelConfirmPopup"
className="popup-overlay"