- 링크결제_분리승인 페이지 추가

- KeyIn결제 FormData 생성
- 링크결제_분리승인 과련 Css 추가
This commit is contained in:
HyeonJongKim
2025-10-15 16:11:39 +09:00
parent 368b553bda
commit 097b1feb0f
20 changed files with 1171 additions and 136 deletions

View File

@@ -95,7 +95,6 @@ export const LinkPaymentApplyConfirmPage = () => {
onClick={() => onClickToConfirm()}
> </button>
</div>
<div className="home-indicator"></div>
</main>
</>
);

View File

@@ -111,6 +111,7 @@ export const LinkPaymentApplyPage = () => {
</div>
</div>
</main>
</>
);
};

View File

@@ -11,7 +11,7 @@ export const LinkPaymentApplySuccessPage = () => {
useSetFooterMode(false);
const onClickToHome = () => {
navigate(PATHS.home);
navigate(PATHS.additionalService.linkPayment.base);
};
return (

View File

@@ -76,7 +76,7 @@ export const LinkPaymentDetailPage = () => {
setShowPayment(!showPayment);
};
const onClickToCancel = () => {
const onClickToResend = () => {
let msg = '재발송 하시겠습니까?';
overlay.open(({
@@ -96,6 +96,12 @@ export const LinkPaymentDetailPage = () => {
);
});
};
const onClickToSeparateApproval = () => {
navigate(PATHS.additionalService.linkPayment.separateApproval, {
state: { mid, tid }
});
};
useEffect(() => {
callDetail();
}, []);
@@ -122,12 +128,18 @@ export const LinkPaymentDetailPage = () => {
detailInfo={detailInfo}
></DetailInfoWrap>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={() => onClickToCancel()}
></button>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={() => onClickToSeparateApproval()}
> </button>
</div>
{/* <div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={() => onClickToResend()}
>재발송</button>
</div> */}
</div>
</div>
</main >

View File

@@ -0,0 +1,69 @@
import { motion } from 'framer-motion';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { PATHS } from "@/shared/constants/paths";
import {
FilterMotionDuration,
FilterMotionStyle,
FilterMotionVariants
} from '@/entities/common/model/constant';
export interface LinkPaymentApplyFailPageProps {
pageOn: boolean;
setPageOn: (pageOn: boolean) => void;
errorMessage?: string;
}
export const LinkPaymentApplyFailPage = ({
pageOn,
setPageOn,
errorMessage
}: LinkPaymentApplyFailPageProps) => {
const { navigate } = useNavigate();
const onClickToClose = () => {
setPageOn(false);
navigate(PATHS.additionalService.linkPayment.shippingHistory);
};
return (
<>
<motion.div
className="full-menu-modal"
initial="hidden"
animate={(pageOn) ? 'visible' : 'hidden'}
variants={FilterMotionVariants}
transition={FilterMotionDuration}
style={{ ...FilterMotionStyle, overflow: 'hidden' }}
>
<div className="full-menu-container" style={{ justifyContent: 'center', alignItems: 'center' }}>
<div className="success-page">
<div className="success-body">
<div
className="error-icon"
aria-hidden="true"
></div>
<h1 className="success-title">
<span>_분리승인</span>
<br />
<span> </span>
</h1>
<div className="success-result">
<p className="result-text align-left position_label">
<span> :</span>
<span>{errorMessage || '다시 시도해 주세요'}</span>
</p>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={onClickToClose}
></button>
</div>
</div>
</div>
</motion.div>
</>
);
};

View File

@@ -0,0 +1,297 @@
import { 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 {
useSetOnBack,
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
import { ExtendedPeriodBottomSheet } from '@/entities/additional-service/ui/link-payment/bottom-sheet/extended-period-bottom-sheet';
import { LinkBreakBottomSheet } from '@/entities/additional-service/ui/link-payment/bottom-sheet/link-break-bottom-sheet';
import { LinkPaymentApplySuccessPage } from './link-payment-separate-approval-success-page';
import { LinkPaymentApplyFailPage } from './link-payment-separate-approval-fail';
interface SeparateApprovalItem {
tid: string;
merchantId: string;
amount: number;
status: string;
validityPeriod: string;
approvalCount: number;
}
export const LinkPaymentSeparateApprovalPage = () => {
const { navigate } = useNavigate();
const [selectedItems, setSelectedItems] = useState<string[]>([]);
const [extendedPeriodBottomSheetOn, setExtendedPeriodBottomSheetOn] = useState<boolean>(false);
const [linkBreakBottomSheetOn, setLinkBreakBottomSheetOn] = useState<boolean>(false);
const [successPageOn, setSuccessPageOn] = useState<boolean>(false);
const [failPageOn, setFailPageOn] = useState<boolean>(false);
const [resultMessage, setResultMessage] = useState<string>('');
const [errorMessage, setErrorMessage] = useState<string>('');
const [mainItem] = useState<SeparateApprovalItem>({
tid: 'NLPDAR2025101315330',
merchantId: '100,000',
amount: 100000,
status: '활성화',
validityPeriod: '2025/10/13',
approvalCount: 0
});
const [additionalItems] = useState<SeparateApprovalItem[]>([
{
tid: '',
merchantId: '(NNN,NNN)',
amount: 0,
status: '(결제상태)',
validityPeriod: 'YYYY/MM/DD',
approvalCount: 0
}
]);
useSetHeaderTitle('분리승인 상세');
useSetHeaderType(HeaderType.RightClose);
useSetOnBack(() => {
navigate(PATHS.additionalService.linkPayment.shippingHistory);
});
useSetFooterMode(false);
const handleCheckboxChange = (tid: string) => {
setSelectedItems(prev => {
if (prev.includes(tid)) {
return prev.filter(id => id !== tid);
} else {
return [...prev, tid];
}
});
};
const handleMainCheckboxChange = () => {
const mainTid = mainItem.tid;
setSelectedItems(prev => {
if (prev.includes(mainTid)) {
return prev.filter(id => id !== mainTid);
} else {
return [...prev, mainTid];
}
});
};
const handleAdditionalCheckboxChange = (index: number) => {
const itemTid = `additional-${index}`;
handleCheckboxChange(itemTid);
};
const onClickToValidityPeriod = () => {
// 기간연장 바텀시트 열기
setExtendedPeriodBottomSheetOn(true);
};
const onClickToSendLink = () => {
// 링크중단 바텀시트 열기
setLinkBreakBottomSheetOn(true);
};
const handleExtendPeriod = () => {
// 바텀시트 닫기
setExtendedPeriodBottomSheetOn(false);
// 기간연장 API 호출 로직
console.log('기간연장 실행', selectedItems);
// TODO: 실제 API 호출
const apiCallSuccess = true; // 임시로 성공으로 설정
if (apiCallSuccess) {
setResultMessage('기간이 성공적으로 연장되었습니다');
setSuccessPageOn(true);
} else {
setErrorMessage('기간 연장에 실패했습니다');
setFailPageOn(true);
}
};
const handleLinkBreak = () => {
// 바텀시트 닫기
setLinkBreakBottomSheetOn(false);
// 링크중단 API 호출 로직
console.log('링크중단 실행', selectedItems);
// TODO: 실제 API 호출
const apiCallSuccess = true; // 임시로 성공으로 설정
if (apiCallSuccess) {
setResultMessage('링크가 성공적으로 중단되었습니다');
setSuccessPageOn(true);
} else {
setErrorMessage('링크 중단에 실패했습니다');
setFailPageOn(true);
}
};
useEffect(() => {
// API 호출하여 데이터 로드
// const { mid, tid } = location.state || {};
}, []);
return (
<>
<main className="separate-approval-main">
<div className="tab-content">
<div className="tab-pane sub active">
<div className="separate-approval-section">
<div className="approval-notice-box">
<p> 기간: 최대 7, 3 </p>
<p> 중단: 유효기간 , , </p>
</div>
<div className="approval-cards-wrapper">
{/* Main Item */}
<div className={`approval-card ${selectedItems.includes(mainItem.tid) ? 'selected' : ''}`}>
<div className="card-header">
<input
type="checkbox"
id={`checkbox-${mainItem.tid}`}
name={`checkbox-${mainItem.tid}`}
checked={selectedItems.includes(mainItem.tid)}
onChange={handleMainCheckboxChange}
className="card-checkbox"
/>
<span className="card-tag">[MAIN]</span>
<span className="card-tid">{mainItem.tid}</span>
</div>
<div className="card-body">
<ul className="info-list">
<li>
<span className="label"> :</span>
<span className="value">{mainItem.merchantId}</span>
</li>
<li>
<span className="label"> :</span>
<span className="value">{mainItem.status}</span>
</li>
<li>
<span className="label"> :</span>
<span className="value">{mainItem.validityPeriod}</span>
</li>
<li>
<span className="label"> :</span>
<span className="value">{mainItem.approvalCount}</span>
</li>
</ul>
</div>
<div className="card-footer">
<div className="period-selector">
<label></label>
<select>
<option value=""></option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
</select>
</div>
</div>
</div>
{/* Additional Items */}
{additionalItems.map((item, index) => (
<div key={index} className={`approval-card ${selectedItems.includes(`additional-${index}`) ? 'selected' : ''}`}>
<input
type="checkbox"
checked={selectedItems.includes(`additional-${index}`)}
onChange={() => handleAdditionalCheckboxChange(index)}
className="card-checkbox"
/>
<div className="card-header">
<span className="card-tag">()</span>
<span className="card-tig"></span>
</div>
<div className="card-body">
<ul className="info-list">
<li>
<span className="label"> :</span>
<span className="value">{item.merchantId}</span>
</li>
<li>
<span className="label"> :</span>
<span className="value">{item.status}</span>
</li>
<li>
<span className="label"> :</span>
<span className="value">{item.validityPeriod}</span>
</li>
<li>
<span className="label"> :</span>
<span className="value">(N)</span>
</li>
</ul>
</div>
<div className="card-footer">
<div className="period-selector">
<label></label>
<select>
<option value=""></option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
</select>
</div>
</div>
</div>
))}
</div>
</div>
<div className="apply-row two-button">
<button
className="btn-50 btn-blue flex-1"
onClick={onClickToValidityPeriod}
>
</button>
<button
className="btn-50 btn-blue flex-1"
onClick={onClickToSendLink}
>
</button>
</div>
</div>
</div>
</main>
<ExtendedPeriodBottomSheet
bottomSheetOn={extendedPeriodBottomSheetOn}
setBottomSheetOn={setExtendedPeriodBottomSheetOn}
extendPeriod={handleExtendPeriod}
/>
<LinkBreakBottomSheet
bottomSheetOn={linkBreakBottomSheetOn}
setBottomSheetOn={setLinkBreakBottomSheetOn}
linkBreak={handleLinkBreak}
/>
<LinkPaymentApplySuccessPage
pageOn={successPageOn}
setPageOn={setSuccessPageOn}
resultMessage={resultMessage}
/>
<LinkPaymentApplyFailPage
pageOn={failPageOn}
setPageOn={setFailPageOn}
errorMessage={errorMessage}
/>
</>
);
};

View File

@@ -0,0 +1,66 @@
import { motion } from 'framer-motion';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { PATHS } from "@/shared/constants/paths";
import {
FilterMotionDuration,
FilterMotionStyle,
FilterMotionVariants
} from '@/entities/common/model/constant';
export interface LinkPaymentApplySuccessPageProps {
pageOn: boolean;
setPageOn: (pageOn: boolean) => void;
resultMessage?: string;
}
export const LinkPaymentApplySuccessPage = ({
pageOn,
setPageOn,
resultMessage
}: LinkPaymentApplySuccessPageProps) => {
const { navigate } = useNavigate();
const onClickToClose = () => {
setPageOn(false);
navigate(PATHS.additionalService.linkPayment.shippingHistory);
};
return (
<>
<motion.div
className="full-menu-modal"
initial="hidden"
animate={(pageOn) ? 'visible' : 'hidden'}
variants={FilterMotionVariants}
transition={FilterMotionDuration}
style={{ ...FilterMotionStyle, overflow: 'hidden' }}
>
<div className="full-menu-container" style={{ paddingTop: '0px' }}>
<div className="success-page">
<div className="success-body">
<div
className="success-icon"
aria-hidden="true"
></div>
<h1 className="success-title">
<span>_분리승인</span>
</h1>
<div className="success-result">
<p className="result-text align-left position_label">
<span> :</span>
<span>{resultMessage || '성공적으로 처리되었습니다'}</span>
</p>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={onClickToClose}
></button>
</div>
</div>
</div>
</motion.div>
</>
);
};