- 부가서비스 링크결제 : 기간연장,링크 중단 활성화,비활성화 조건 적용, 체크박스 Action 추가

This commit is contained in:
HyeonJongKim
2025-10-30 09:29:13 +09:00
parent bc1540ca04
commit 133aa44dff
5 changed files with 221 additions and 89 deletions

View File

@@ -52,17 +52,9 @@ export const LinkPaymentApplyConfirmPage = () => {
if (rs.status) {
navigate(PATHS.additionalService.linkPayment.confirmSuccess);
} else {
// 응답은 성공했지만 status가 false인 경우
const validationErrors = rs.error?.details?.validationErrors;
if (validationErrors) {
// validation 에러 메시지들을 수집
const errorMessages = Object.values(validationErrors).join('\n');
snackBar(`[실패] ${errorMessages}`);
} else {
// 일반 에러 메시지
const errorMessage = rs.error?.message || '요청을 처리할 수 없습니다.';
snackBar(`[실패] ${errorMessage}`);
}
// 일반 에러 메시지
const errorMessage = rs.error?.message || '요청을 처리할 수 없습니다.';
snackBar(`[실패] ${errorMessage}`);
}
})
.catch((error) => {

View File

@@ -157,7 +157,7 @@ export const LinkPaymentDetailPage = () => {
<button
className="btn-50 btn-blue flex-1"
onClick={() => onClickToSeparateApproval()}
disabled={false}
disabled={detailExposure}
> </button>
</div>
</div>

View File

@@ -52,11 +52,63 @@ export const LinkPaymentSeparateApprovalPage = () => {
const handleCheckboxChange = (subRequestId: string) => {
setSelectedItems(prev => {
if (prev.includes(subRequestId)) {
return prev.filter(id => id !== subRequestId);
} else {
return [...prev, subRequestId];
// 현재 클릭한 item 찾기
const target = items.find(i => i.subRequestId === subRequestId)
|| items.find((_, idx) => `item-${idx}` === subRequestId);
if (!target) return prev;
// 동일한 requestId를 가진 MAIN, SUB 그룹 가져오기
const sameRequestIdItems = items.filter(i => i.requestId === target.requestId);
const mainItem = sameRequestIdItems.find(i => i.type === LinkPaymentSeparateType.MAIN);
// PENDING 상태인 SUB만 필터링
const subItems = sameRequestIdItems.filter(i =>
i.type === LinkPaymentSeparateType.SUB && i.paymentStatus === 'PENDING'
);
const mainId = mainItem?.subRequestId || (mainItem ? `item-${items.indexOf(mainItem)}` : '');
const subIds = subItems.map(i => i.subRequestId || `item-${items.indexOf(i)}`);
// -----------------------------
// MAIN 클릭 시
// -----------------------------
if (target.type === LinkPaymentSeparateType.MAIN) {
const isMainChecked = prev.includes(mainId);
if (isMainChecked) {
// MAIN 해제 → PENDING인 SUB만 해제
return prev.filter(id => ![mainId, ...subIds].includes(id));
} else {
// MAIN 체크 → PENDING인 SUB만 추가
return [...new Set([...prev, mainId, ...subIds])];
}
}
// -----------------------------
// SUB 클릭 시
// -----------------------------
const isChecked = prev.includes(subRequestId);
let updated: string[] = [];
if (isChecked) {
// SUB 해제
updated = prev.filter(id => id !== subRequestId);
// 해제 후 남은 PENDING SUB 중 하나라도 빠져 있으면 MAIN도 해제
const allPendingSubsChecked = subIds.every(id => updated.includes(id));
if (!allPendingSubsChecked && mainId) {
updated = updated.filter(id => id !== mainId);
}
} else {
// SUB 체크
updated = [...prev, subRequestId];
// 모든 PENDING SUB 체크되면 MAIN도 자동 체크
const allPendingSubsChecked = subIds.every(id => updated.includes(id));
if (allPendingSubsChecked && mainId) {
updated = [...new Set([...updated, mainId])];
}
}
return updated;
});
};
@@ -67,12 +119,60 @@ export const LinkPaymentSeparateApprovalPage = () => {
}));
};
// 기간연장 버튼 활성화 조건: 선택된 항목이 있고, 모든 선택된 항목의 연장기간 설정
const isExtendButtonEnabled = () => {
if (selectedItems.length === 0) return false;
return selectedItems.every(id => extendPeriods[id] && extendPeriods[id] !== '');
// 각 SUB 항목이 연장기간 설정 가능한지 체크
const canExtendPeriod = (item: ExtensionLinkPaySeparateDetailItem) => {
const today = moment().format('YYYYMMDD');
const paymentLimitDate = item.paymentLimitDate || '';
const paymentLimitCount = item.paymentLimitCount || 0;
// 연장횟수 < 3 이고 유효일자 >= 조회일자
return paymentLimitCount < 3 && paymentLimitDate >= today;
};
// 각 SUB 항목이 링크중단 가능한지 체크
const canLinkBreak = (item: ExtensionLinkPaySeparateDetailItem) => {
const today = moment().format('YYYYMMDD');
const paymentLimitDate = item.paymentLimitDate || '';
// 유효일자 >= 조회일자
return paymentLimitDate >= today;
};
const isExtendButtonEnabled = () => {
// MAIN 제외, 체크된 SUB 중에서
const checkedSubs = items.filter((i, index) => {
const itemId = i.subRequestId || `item-${index}`;
return i.type === LinkPaymentSeparateType.SUB &&
selectedItems.includes(itemId) &&
i.paymentStatus === 'PENDING';
});
// 체크된 SUB가 없으면 비활성화
if (checkedSubs.length === 0) return false;
// 모든 체크된 SUB가 연장 가능하고, 연장기간이 설정되어 있어야 함
return checkedSubs.every((sub) => {
const itemId = sub.subRequestId || `item-${items.indexOf(sub)}`;
return canExtendPeriod(sub) && extendPeriods[itemId] && extendPeriods[itemId] !== '';
});
};
const isLinkBreadkEnabled = () => {
// MAIN 제외, 체크된 SUB 중에서
const checkedSubs = items.filter((i, index) => {
const itemId = i.subRequestId || `item-${index}`;
return i.type === LinkPaymentSeparateType.SUB &&
selectedItems.includes(itemId) &&
i.paymentStatus === 'PENDING';
});
// 체크된 SUB가 없으면 비활성화
if (checkedSubs.length === 0) return false;
// 모든 체크된 SUB가 링크중단 가능해야 함
return checkedSubs.every(sub => canLinkBreak(sub));
}
const onClickToValidityPeriod = () => {
// 기간연장 바텀시트 열기
setExtendedPeriodBottomSheetOn(true);
@@ -94,8 +194,13 @@ export const LinkPaymentSeparateApprovalPage = () => {
return;
}
// 모든 선택된 항목들의 연장 기간이 설정되었는지 확인
const allHaveExtendPeriod = selectedItems.every(id => extendPeriods[id]);
// MAIN을 제외한 선택된 SUB 항목들의 연장 기간이 설정되었는지 확인
const selectedSubItems = selectedItems.filter(id => {
const item = items.find(i => (i.subRequestId || `item-${items.indexOf(i)}`) === id);
return item && item.type === LinkPaymentSeparateType.SUB;
});
const allHaveExtendPeriod = selectedSubItems.every(id => extendPeriods[id]);
if (!allHaveExtendPeriod) {
setErrorMessage('모든 선택된 항목의 연장 기간을 선택해주세요.');
@@ -103,15 +208,15 @@ export const LinkPaymentSeparateApprovalPage = () => {
return;
}
// 첫 번째 선택된 항목의 연장 기간 사용 (모든 항목이 같은 기간으로 연장)
const firstSelectedId = selectedItems[0] as string;
const extendDays = extendPeriods[firstSelectedId] as string;
// 첫 번째 선택된 SUB 항목의 연장 기간 사용 (모든 항목이 같은 기간으로 연장)
const firstSelectedSubId = selectedSubItems[0] as string;
const extendDays = extendPeriods[firstSelectedSubId] as string;
// 연장 날짜 계산 (오늘 날짜 + 연장일수)
const extendDate = moment().add(parseInt(extendDays), 'days').format('YYYYMMDD');
// selectedItems를 API 형식으로 변환
const selectedItemsData = selectedItems.map(itemId => {
// MAIN을 제외한 SUB 항목만 API 형식으로 변환
const selectedItemsData = selectedSubItems.map(itemId => {
const item = items.find(i => (i.subRequestId || `item-${items.indexOf(i)}`) === itemId);
return {
type: item?.type || '',
@@ -157,7 +262,13 @@ export const LinkPaymentSeparateApprovalPage = () => {
return;
}
const selectedItemsData = selectedItems.map(itemId => {
// MAIN을 제외한 SUB 항목만 필터링
const selectedSubItems = selectedItems.filter(id => {
const item = items.find(i => (i.subRequestId || `item-${items.indexOf(i)}`) === id);
return item && item.type === LinkPaymentSeparateType.SUB;
});
const selectedItemsData = selectedSubItems.map(itemId => {
const item = items.find(i => (i.subRequestId || `item-${items.indexOf(i)}`) === itemId);
return {
type: item?.type || '',
@@ -230,67 +341,75 @@ export const LinkPaymentSeparateApprovalPage = () => {
<div className="approval-cards-wrapper">
{items.map((item, index) => {
const itemId = item.subRequestId || `item-${index}`;
const isDisabled = item.paymentStatus !== 'PENDING';
return (
<div key={itemId} className={`approval-card ${selectedItems.includes(itemId) ? 'selected' : ''}`}>
<div className="card-header">
<input
type="checkbox"
id={`checkbox-${itemId}`}
name={`checkbox-${itemId}`}
checked={selectedItems.includes(itemId)}
onChange={() => handleCheckboxChange(itemId)}
className="card-checkbox"
/>
<span className="card-tag">[{item.type}]</span>
<span className="card-tid">{item.moid}</span>
</div>
<div className="card-body">
<ul className="info-list">
<li>
<span className="label"> :</span>
<span className="value">{item.amount.toLocaleString()}</span>
</li>
<li>
<span className="label"> :</span>
<span className="value">{item.paymentStatusName}</span>
</li>
{item.type !== LinkPaymentSeparateType.MAIN && (
<>
<li>
<span className="label"> :</span>
<span className="value">
{item.paymentLimitDate
? moment(item.paymentLimitDate, 'YYYYMMDD').format('YYYY/MM/DD')
: '-'
}
</span>
</li>
<li>
<span className="label"> :</span>
<span className="value">{item.paymentLimitCount}</span>
</li>
</>
)}
</ul>
</div>
<div className="card-footer">
<div className="period-selector">
<label></label>
<select
value={extendPeriods[itemId] || ''}
onChange={(e) => handleExtendPeriodChange(itemId, e.target.value)}
disabled={item.type === LinkPaymentSeparateType.MAIN}
>
<option value=""></option>
{[1, 2, 3, 4, 5, 6, 7].map(days => {
const baseDate = moment(item.paymentLimitDate, 'YYYYMMDD');
const targetDate = baseDate.clone().add(days, 'days').format('YYYY/MM/DD');
return <option key={days} value={days.toString()}>{targetDate}</option>;
})}
</select>
<div key={itemId} className={`approval-card ${selectedItems.includes(itemId) ? 'selected' : ''} ${isDisabled ? 'disabled' : ''}`}>
<div className="card-header">
<input
type="checkbox"
id={`checkbox-${itemId}`}
name={`checkbox-${itemId}`}
checked={selectedItems.includes(itemId)}
onChange={() => handleCheckboxChange(itemId)}
className="card-checkbox"
disabled={item.paymentStatus !== 'PENDING'}
/>
<span className="card-tag">[{item.type}]</span>
<span className="card-tid">{item.moid}</span>
</div>
<div className="card-body">
<ul className="info-list">
<li>
<span className="label"> :</span>
<span className="value">{item.amount.toLocaleString()}</span>
</li>
<li>
<span className="label"> :</span>
<span className="value">{item.paymentStatusName}</span>
</li>
{item.type !== LinkPaymentSeparateType.MAIN && (
<>
<li>
<span className="label"> :</span>
<span className="value">
{item.paymentLimitDate
? moment(item.paymentLimitDate, 'YYYYMMDD').format('YYYY/MM/DD')
: '-'
}
</span>
</li>
<li>
<span className="label"> :</span>
<span className="value">{item.paymentLimitCount}</span>
</li>
</>
)}
</ul>
</div>
<div className="card-footer">
<div className="period-selector">
<label></label>
<select
value={extendPeriods[itemId] || ''}
onChange={(e) => handleExtendPeriodChange(itemId, e.target.value)}
disabled={item.type === LinkPaymentSeparateType.MAIN || item.paymentStatus !== "PENDING" || !canExtendPeriod(item)}
>
<option value="">
{item.type === LinkPaymentSeparateType.MAIN
? '-'
: !canExtendPeriod(item)
? '-'
: '미설정'}
</option>
{canExtendPeriod(item) && [1, 2, 3, 4, 5, 6, 7].map(days => {
const baseDate = moment(item.paymentLimitDate, 'YYYYMMDD');
const targetDate = baseDate.clone().add(days, 'days').format('YYYY/MM/DD');
return <option key={days} value={days.toString()}>{targetDate}</option>;
})}
</select>
</div>
</div>
</div>
</div>
);
})}
</div>
@@ -306,7 +425,7 @@ export const LinkPaymentSeparateApprovalPage = () => {
<button
className="btn-50 btn-blue flex-1"
onClick={onClickToSendLink}
disabled={selectedItems.length === 0}
disabled={!isLinkBreadkEnabled()}
>
</button>
</div>

View File

@@ -1,6 +1,4 @@
import { motion } from 'framer-motion';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { PATHS } from "@/shared/constants/paths";
import {
FilterMotionDuration,
FilterMotionStyle,

View File

@@ -233,6 +233,29 @@ main.home-main{
box-shadow: 0 4px 12px rgba(62, 106, 252, 0.2);
}
/* PENDING이 아닌 항목 비활성화 스타일 */
.approval-card.disabled {
opacity: 0.5;
background: var(--color-F9F9F9);
pointer-events: none;
}
.approval-card.disabled .card-checkbox {
cursor: not-allowed;
background-color: var(--color-E6E6E6);
border-color: var(--color-CCCCCC);
}
.approval-card.disabled .card-tid,
.approval-card.disabled .info-list .value {
color: var(--color-999999);
}
.approval-card.disabled .period-selector select {
background-color: var(--color-F3F3F3);
cursor: not-allowed;
}
.approval-card .card-checkbox {
display: block !important;
position: absolute;