부가서비스 : 링크결제 발송내역,발송대기 상세페이지 추가
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import { useState } from 'react';
|
||||
import { PATHS } from '@/shared/constants/paths';
|
||||
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
|
||||
import { useSetOnBack } from '@/widgets/sub-layout/use-sub-layout';
|
||||
import { IMAGE_ROOT } from '@/shared/constants/common';
|
||||
|
||||
export const LinkPaymentStep1 = () => {
|
||||
const { navigate } = useNavigate();
|
||||
|
||||
useSetOnBack(() => {
|
||||
navigate(PATHS.additionalService.intro);
|
||||
});
|
||||
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState('SMS');
|
||||
const [formData, setFormData] = useState({
|
||||
merchant: 'nictest001m',
|
||||
productName: '',
|
||||
productPrice: '',
|
||||
orderNumber: '',
|
||||
validDate: '2025.06.30'
|
||||
});
|
||||
|
||||
const handlePaymentMethodChange = (method: string) => {
|
||||
setSelectedPaymentMethod(method);
|
||||
};
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[field]: value
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="issue-form">
|
||||
<div className="issue-row gap-10">
|
||||
<div className="issue-label">가맹점</div>
|
||||
<div className="issue-field">
|
||||
<select
|
||||
className="wid-100"
|
||||
value={formData.merchant}
|
||||
onChange={(e) => handleInputChange('merchant', e.target.value)}
|
||||
>
|
||||
<option>nictest001m</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="issue-row gap-10">
|
||||
<div className="issue-label">발송 수단</div>
|
||||
<div className="issue-field">
|
||||
<div className="chip-row">
|
||||
<span
|
||||
className={`keyword-tag flex-1 ${selectedPaymentMethod === 'SMS' ? 'active' : ''}`}
|
||||
onClick={() => handlePaymentMethodChange('SMS')}
|
||||
>
|
||||
SMS
|
||||
</span>
|
||||
<span
|
||||
className={`keyword-tag flex-1 ${selectedPaymentMethod === '이메일' ? 'active' : ''}`}
|
||||
onClick={() => handlePaymentMethodChange('이메일')}
|
||||
>
|
||||
이메일
|
||||
</span>
|
||||
<span
|
||||
className={`keyword-tag flex-1 ${selectedPaymentMethod === '카카오' ? 'active' : ''}`}
|
||||
onClick={() => handlePaymentMethodChange('카카오')}
|
||||
>
|
||||
카카오
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="issue-row gap-10">
|
||||
<div className="issue-label">상품명</div>
|
||||
<div className="issue-field">
|
||||
<input
|
||||
type="text"
|
||||
placeholder=""
|
||||
value={formData.productName}
|
||||
onChange={(e) => handleInputChange('productName', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="issue-row gap-10">
|
||||
<div className="issue-label">상품가격</div>
|
||||
<div className="issue-field">
|
||||
<input
|
||||
type="text"
|
||||
placeholder=""
|
||||
value={formData.productPrice}
|
||||
onChange={(e) => handleInputChange('productPrice', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="issue-row gap-10">
|
||||
<div className="issue-label">상품 주문번호</div>
|
||||
<div className="issue-field">
|
||||
<input
|
||||
type="text"
|
||||
placeholder=""
|
||||
value={formData.orderNumber}
|
||||
onChange={(e) => handleInputChange('orderNumber', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="issue-row gap-10">
|
||||
<div className="issue-label">결제 유효일</div>
|
||||
<div className="issue-field">
|
||||
<div className="link-apply-date">
|
||||
<div className="input-wrapper date">
|
||||
<input
|
||||
type="text"
|
||||
value={formData.validDate}
|
||||
className="date-input"
|
||||
onChange={(e) => handleInputChange('validDate', e.target.value)}
|
||||
/>
|
||||
<button type="button" className="date-btn">
|
||||
<img
|
||||
src={IMAGE_ROOT + '/ico_date.svg'}
|
||||
alt="날짜 선택"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<span>까지</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,164 @@
|
||||
import {ProcessStep} from "@/entities/transaction/model/types";
|
||||
import {useSetOnBack} from "@/widgets/sub-layout/use-sub-layout";
|
||||
import {useState} from "react";
|
||||
|
||||
export interface LinkPaymentStep2Props {
|
||||
setProcessStep: ((processStep: ProcessStep) => void);
|
||||
}
|
||||
|
||||
export const LinkPaymentStep2 = ({
|
||||
setProcessStep
|
||||
}: LinkPaymentStep2Props) => {
|
||||
useSetOnBack(() => {
|
||||
setProcessStep(ProcessStep.One);
|
||||
});
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[field]: value
|
||||
}));
|
||||
};
|
||||
|
||||
const [selectedPurchaserType, setSelectedPurchaserType] = useState('개인');
|
||||
const [selectedLanguage, setSelectedLanguage] = useState('국문')
|
||||
const [selectedLinkContent, setSelectedLinkContent] = useState('기본');
|
||||
const [formData, setFormData] = useState({
|
||||
purchaser: '',
|
||||
purchaserEmail: '',
|
||||
purchaserPhone: '',
|
||||
purchaserBirth: ''
|
||||
});
|
||||
|
||||
const handlePurchaserTypeChange = (type: string) => {
|
||||
setSelectedPurchaserType(type)
|
||||
}
|
||||
|
||||
const handleLanguageType = (type: string) => {
|
||||
setSelectedLanguage(type)
|
||||
}
|
||||
|
||||
const handleLinkContent = (content: string) => {
|
||||
setSelectedLinkContent(content)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="issue-form">
|
||||
<div className="issue-row gap-10">
|
||||
<div className="issue-label wid-105">구매자명</div>
|
||||
<div className="issue-field">
|
||||
<input
|
||||
type="text"
|
||||
placeholder=""
|
||||
value={formData.purchaser}
|
||||
onChange={(e) => handleInputChange('purchaser', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="issue-row gap-10">
|
||||
<div className="issue-label wid-105">구매자 이메일</div>
|
||||
<div className="issue-field">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="test@nicepay.co.kr"
|
||||
value={formData.purchaserEmail}
|
||||
onChange={(e) => handleInputChange('purchaserEmail', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="issue-row gap-10">
|
||||
<div className="issue-label wid-105">구매자<br/>휴대폰 번호</div>
|
||||
<div className="issue-field">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="01012345678"
|
||||
value={formData.purchaserPhone}
|
||||
onChange={(e) => handleInputChange('purchaserPhone', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="issue-row gap-10 beetween">
|
||||
<div className="issue-label wid-105">구매자 정보 대조</div>
|
||||
<label className="settings-switch">
|
||||
<input type="checkbox"/>
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="issue-row gap-10">
|
||||
<div className="issue-label wid-105"></div>
|
||||
<div className="issue-field">
|
||||
<div className="chip-row">
|
||||
<span
|
||||
className={`keyword-tag flex-1 ${selectedPurchaserType === '개인' ? 'active' : ''}`}
|
||||
onClick={() => handlePurchaserTypeChange('개인')}
|
||||
>
|
||||
개인
|
||||
</span>
|
||||
<span
|
||||
className={`keyword-tag flex-1 ${selectedPurchaserType === '법인' ? 'active' : ''}`}
|
||||
onClick={() => handlePurchaserTypeChange('법인')}
|
||||
>
|
||||
법인
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="issue-row gap-10">
|
||||
<div className="issue-label wid-105"></div>
|
||||
<div className='issue-field'>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="생년월일 6자리"
|
||||
value={formData.purchaserBirth}
|
||||
onChange={(e) => handleInputChange('purchaserPhone', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="issue-row gap-10">
|
||||
<div className="issue-label wid-105">언어</div>
|
||||
<div className="issue-field">
|
||||
<div className="chip-row">
|
||||
<span
|
||||
className={`keyword-tag flex-1 ${selectedLanguage === '국문' ? 'active' : ''}`}
|
||||
onClick={() => handleLanguageType('국문')}
|
||||
>
|
||||
국문
|
||||
</span>
|
||||
<span
|
||||
className={`keyword-tag flex-1 ${selectedLanguage === '영문' ? 'active' : ''}`}
|
||||
onClick={() => handleLanguageType('영문')}
|
||||
>
|
||||
영문
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="issue-row gap-10">
|
||||
<div className="issue-label wid-105">링크내용</div>
|
||||
<div className="issue-field">
|
||||
<div className="chip-row">
|
||||
<span
|
||||
className={`keyword-tag flex-1 ${selectedLinkContent === '기본' ? 'active' : ''}`}
|
||||
onClick={() => handleLinkContent('기본')}
|
||||
>
|
||||
기본
|
||||
</span>
|
||||
<span
|
||||
className={`keyword-tag flex-1 ${selectedLinkContent === '추가' ? 'active' : ''}`}
|
||||
onClick={() => handleLinkContent('추가')}
|
||||
>
|
||||
추가
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
interface DetailDeetsInfoSectionProps {
|
||||
deetsInfo?: any;
|
||||
show?: boolean;
|
||||
onClickToShowInfo?: () => void;
|
||||
}
|
||||
|
||||
export const DetailDeetsInfoSection = ({
|
||||
deetsInfo,
|
||||
show,
|
||||
onClickToShowInfo
|
||||
}: DetailDeetsInfoSectionProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="txn-section">
|
||||
<div className="section-title">상세 정보</div>
|
||||
<ul className="kv-list">
|
||||
<li className="kv-row">
|
||||
<span className="k">이메일</span>
|
||||
<span className="v"></span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">휴대폰번호</span>
|
||||
<span className="v">01073937470</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">상품명</span>
|
||||
<span className="v">곰돌이</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">주문번호</span>
|
||||
<span className="v">mod201705545050</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
interface DetailPaymentInfoSectionProps {
|
||||
paymentInfo?: any;
|
||||
show?: boolean;
|
||||
onClickToShowInfo?: () => void;
|
||||
}
|
||||
|
||||
export const DetailPaymentInfoSection = ({
|
||||
paymentInfo,
|
||||
show,
|
||||
onClickToShowInfo
|
||||
}: DetailPaymentInfoSectionProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="txn-section">
|
||||
<div className="section-title">결제 정보</div>
|
||||
<ul className="kv-list">
|
||||
<li className="kv-row">
|
||||
<span className="k">구매자명</span>
|
||||
<span className="v">김*환</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">발송수단</span>
|
||||
<span className="v">SMS</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">발송일자</span>
|
||||
<span className="v">2025.06.08</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">결제상태(실패횟수)</span>
|
||||
<span className="v">미완료(2)</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">결제수단</span>
|
||||
<span className="v">신용카드</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">결제일자</span>
|
||||
<span className="v"></span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">결제유효일자</span>
|
||||
<span className="v">2025.06.08</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
interface DetailPendingPaymentInfoSectionProps {
|
||||
paymentInfo?: any;
|
||||
show?: boolean;
|
||||
onClickToShowInfo?: () => void;
|
||||
}
|
||||
|
||||
export const DetailPaymentInfoSection = ({
|
||||
paymentInfo,
|
||||
show,
|
||||
onClickToShowInfo
|
||||
}: DetailPendingPaymentInfoSectionProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="txn-section">
|
||||
<div className="section-title">결제 정보</div>
|
||||
<ul className="kv-list">
|
||||
<li className="kv-row">
|
||||
<span className="k">진행상태</span>
|
||||
<span className="v">발송요청</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">요청일자</span>
|
||||
<span className="v">2025.06.05</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">결제유효일자</span>
|
||||
<span className="v">2025.06.08</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">발송수단</span>
|
||||
<span className="v">SMS</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">구매자명</span>
|
||||
<span className="v">김*환</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">이메일</span>
|
||||
<span className="v"></span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">휴대폰번호</span>
|
||||
<span className="v">01073937470</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">상품명</span>
|
||||
<span className="v">곰돌이</span>
|
||||
</li>
|
||||
<li className="kv-row">
|
||||
<span className="k">주문번호</span>
|
||||
<span className="v">moid201705545050</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import moment from 'moment';
|
||||
import 'moment/dist/locale/ko';
|
||||
import { LinkPaymentItem } from './link-payment-item';
|
||||
import { JSX } from 'react';
|
||||
|
||||
interface LinkPaymentTransaction {
|
||||
transactionId: string;
|
||||
customerName: string;
|
||||
status: string;
|
||||
channel: string;
|
||||
paymentMethod: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
interface LinkPaymentDateGroupProps {
|
||||
date: string;
|
||||
items: LinkPaymentTransaction[];
|
||||
}
|
||||
|
||||
export const LinkPaymentDateGroup = ({
|
||||
date,
|
||||
items
|
||||
}: LinkPaymentDateGroupProps) => {
|
||||
moment.locale('ko');
|
||||
const getStateDate = () => {
|
||||
let stateDate = moment(date).format('YY.MM.DD(ddd)');
|
||||
return stateDate;
|
||||
};
|
||||
|
||||
const getLinkPaymentItem = () => {
|
||||
const rs: JSX.Element[] = [];
|
||||
if (items && items.length > 0) {
|
||||
items.forEach((item, index) => {
|
||||
const key = 'LinkPaymentItem-' + index;
|
||||
rs.push(
|
||||
<LinkPaymentItem
|
||||
key={key}
|
||||
transactionId={item.transactionId}
|
||||
customerName={item.customerName}
|
||||
status={item.status}
|
||||
channel={item.channel}
|
||||
paymentMethod={item.paymentMethod}
|
||||
amount={item.amount}
|
||||
/>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
return rs;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="date-header">{getStateDate()}</div>
|
||||
{getLinkPaymentItem()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,143 @@
|
||||
import { IMAGE_ROOT } from "@/shared/constants/common";
|
||||
import { useState, useEffect } from "react";
|
||||
import { LinkPaymentFilter } from "./link-payment-filter";
|
||||
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
|
||||
import { PATHS } from "@/shared/constants/paths";
|
||||
import { LinkPaymentList } from "./link-payment-list";
|
||||
import { SortOptionsBox } from "./sort-options-box";
|
||||
import { SortByKeys } from "../../model/types";
|
||||
|
||||
|
||||
export const LinkPaymentDispatchListWrap = () => {
|
||||
const { navigate } = useNavigate();
|
||||
const [filterOn, setFilterOn] = useState<boolean>(false);
|
||||
const [sortBy, setSortBy] = useState<SortByKeys>(SortByKeys.New);
|
||||
const [listItems, setListItems] = useState({});
|
||||
|
||||
const onClickToOpenFilter = () => {
|
||||
setFilterOn(!filterOn);
|
||||
};
|
||||
const onClickToNavigate = () => {
|
||||
navigate(PATHS.additionalService.linkPayment.request)
|
||||
}
|
||||
const onClickToSort = (sort: SortByKeys) => {
|
||||
setSortBy(sort);
|
||||
callList({ sortBy: sort });
|
||||
};
|
||||
|
||||
const callList = (option?: {sortBy?: string, val?: string}) => {
|
||||
setListItems({
|
||||
'20250608': [
|
||||
{
|
||||
transactionId: 'txn1',
|
||||
customerName: '김*환(7000)',
|
||||
status: '결제완료',
|
||||
channel: 'SMS',
|
||||
paymentMethod: '신용카드',
|
||||
amount: 5254000
|
||||
},
|
||||
{
|
||||
transactionId: 'txn2',
|
||||
customerName: '김*환(7000)',
|
||||
status: '결제완료',
|
||||
channel: '이메일',
|
||||
paymentMethod: '신용카드',
|
||||
amount: 5254000
|
||||
},
|
||||
{
|
||||
transactionId: 'txn3',
|
||||
customerName: '김*환(7000)',
|
||||
status: '입금요청',
|
||||
channel: '이메일',
|
||||
paymentMethod: '신용카드',
|
||||
amount: 5254000
|
||||
},
|
||||
{
|
||||
transactionId: 'txn4',
|
||||
customerName: '김*환(7000)',
|
||||
status: '결제중단',
|
||||
channel: 'SMS',
|
||||
paymentMethod: '',
|
||||
amount: 5254000
|
||||
},
|
||||
{
|
||||
transactionId: 'txn5',
|
||||
customerName: '김*환(7000)',
|
||||
status: '결제실패',
|
||||
channel: 'SMS',
|
||||
paymentMethod: '',
|
||||
amount: 5254000
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
callList();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="summary-section">
|
||||
<div className="credit-controls">
|
||||
<div>
|
||||
<input
|
||||
className="credit-period"
|
||||
type="text"
|
||||
value="2025.06.01 ~ 2025.06.30"
|
||||
readOnly={true}
|
||||
/>
|
||||
<button
|
||||
className="filter-btn"
|
||||
aria-label="필터"
|
||||
>
|
||||
<img
|
||||
src={IMAGE_ROOT + '/ico_setting.svg'}
|
||||
alt="검색옵션"
|
||||
onClick={() => onClickToOpenFilter()}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className="download-btn"
|
||||
aria-label="다운로드"
|
||||
>
|
||||
<img
|
||||
src={IMAGE_ROOT + '/ico_download.svg'}
|
||||
alt="다운로드"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="filter-section">
|
||||
<SortOptionsBox
|
||||
sortBy={sortBy}
|
||||
onClickToSort={onClickToSort}
|
||||
>
|
||||
</SortOptionsBox>
|
||||
<div className="excrow">
|
||||
<div className="full-menu-keywords no-padding">
|
||||
<span className="keyword-tag active">전체</span>
|
||||
<span className="keyword-tag">성공</span>
|
||||
<span className="keyword-tag">실패</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<LinkPaymentList
|
||||
listItems={listItems}
|
||||
/>
|
||||
<div className="apply-row">
|
||||
<button
|
||||
className="btn-50 btn-blue flex-1"
|
||||
onClick={() => onClickToNavigate()}
|
||||
>결제 신청</button>
|
||||
</div>
|
||||
<LinkPaymentFilter
|
||||
filterOn={filterOn}
|
||||
setFilterOn={setFilterOn}
|
||||
></LinkPaymentFilter>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import {IMAGE_ROOT} from "@/shared/constants/common";
|
||||
import {LinkPaymentFilterProps} from "@/entities/additional-service/model/types";
|
||||
|
||||
export const LinkPaymentFilter = ({
|
||||
filterOn,
|
||||
setFilterOn
|
||||
}: LinkPaymentFilterProps) => {
|
||||
const variants = {
|
||||
hidden:{ x: '100%'},
|
||||
visible: {x: '0%'}
|
||||
};
|
||||
|
||||
const onClickToClose = () => {
|
||||
setFilterOn(false);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<motion.div
|
||||
id="fullMenuModal"
|
||||
className="full-menu-modal"
|
||||
initial="hidden"
|
||||
animate={ (filterOn)? 'visible': 'hidden' }
|
||||
variants={ variants }
|
||||
transition={{ duration: 0.3 }}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<div className="full-menu-container">
|
||||
<div className="full-menu-header">
|
||||
<div className="full-menu-title center">필터</div>
|
||||
<div className="full-menu-actions">
|
||||
<button
|
||||
id="closeFullMenu"
|
||||
className="full-menu-close"
|
||||
>
|
||||
<img
|
||||
src={ IMAGE_ROOT + '/ico_close.svg' }
|
||||
alt="닫기"
|
||||
onClick={ () => onClickToClose() }
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="option-list pb-120">
|
||||
<div className="opt-field">
|
||||
<div className="opt-label">가맹점</div>
|
||||
<div className="opt-controls">
|
||||
<select className="flex-1">
|
||||
<option>nictest001m</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="opt-field">
|
||||
<div className="opt-label">휴대폰번호, 이메일</div>
|
||||
<div className="opt-controls">
|
||||
<select className="flex-1">
|
||||
<option>휴대폰번호</option>
|
||||
<option>이메일</option>
|
||||
</select>
|
||||
<input
|
||||
type="text"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="opt-field">
|
||||
<div className="opt-label">결제수단</div>
|
||||
<div className="opt-controls">
|
||||
<select className="flex-1">
|
||||
<option>nictext001m</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="opt-field mt_top">
|
||||
<div className="opt-label">조회기간</div>
|
||||
<div className="opt-controls col below h36">
|
||||
<div className="chip-row">
|
||||
<span className="keyword-tag">당일</span>
|
||||
<span className="keyword-tag">일주일</span>
|
||||
<span className="keyword-tag">1개월</span>
|
||||
<span className="keyword-tag active">직접입력</span>
|
||||
</div>
|
||||
|
||||
<div className="range-row">
|
||||
<div className="input-wrapper date">
|
||||
<input
|
||||
className="date-input"
|
||||
type="text"
|
||||
placeholder="날짜 선택"
|
||||
value="2025.06.08"
|
||||
readOnly={ true }
|
||||
/>
|
||||
<button
|
||||
className="date-btn"
|
||||
type="button"
|
||||
>
|
||||
<img
|
||||
src={ IMAGE_ROOT + '/ico_date.svg' }
|
||||
alt="날짜 선택"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<span className="beetween">~</span>
|
||||
<div className="input-wrapper date">
|
||||
<input
|
||||
className="date-input"
|
||||
type="text"
|
||||
placeholder="날짜 선택"
|
||||
value="2025.06.08"
|
||||
readOnly={ true }
|
||||
/>
|
||||
<button
|
||||
className="date-btn"
|
||||
type="button"
|
||||
>
|
||||
<img
|
||||
src={ IMAGE_ROOT + '/ico_date.svg' }
|
||||
alt="날짜 선택"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="opt-field">
|
||||
<div className="opt-label">거래 구분</div>
|
||||
<div className="opt-controls col below h36">
|
||||
<div className="chip-row">
|
||||
<span className="keyword-tag flex-1 active">전체</span>
|
||||
<span className="keyword-tag flex-1">미완료/ <br/> 활성화</span>
|
||||
<span className="keyword-tag flex-1">입금요청</span>
|
||||
<span className="keyword-tag flex-1">결제완료</span>
|
||||
<span
|
||||
className="keyword-tag"
|
||||
style={{ visibility: 'hidden' }}
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="opt-controls col below h50 mt-10">
|
||||
<div className="chip-row">
|
||||
<span className="keyword-tag flex-1">결제실패</span>
|
||||
<span className="keyword-tag flex-1">결제중단/ <br/> 비활성화</span>
|
||||
<span
|
||||
className="keyword-tag"
|
||||
style={{ visibility: 'hidden' }}
|
||||
></span>
|
||||
<span
|
||||
className="keyword-tag"
|
||||
style={{ visibility: 'hidden' }}
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="opt-field">
|
||||
<div className="opt-label">전송결과</div>
|
||||
<div className="opt-controls col below h36">
|
||||
<div className="chip-row">
|
||||
<span className="keyword-tag flex-1 active">전체</span>
|
||||
<span className="keyword-tag flex-1">성공</span>
|
||||
<span className="keyword-tag flex-1">실패</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="opt-field">
|
||||
<div className="opt-label">발송수단</div>
|
||||
<div className="opt-controls col below h36">
|
||||
<div className="chip-row">
|
||||
<span className="keyword-tag flex-1 active">전체</span>
|
||||
<span className="keyword-tag flex-1">SMS</span>
|
||||
<span className="keyword-tag flex-1">이메일</span>
|
||||
<span className="keyword-tag flex-1">카카오</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="apply-row">
|
||||
<button className="btn-50 btn-blue flex-1">적용</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { PATHS } from '@/shared/constants/paths';
|
||||
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
|
||||
import moment from 'moment';
|
||||
|
||||
interface LinkPaymentItemProps {
|
||||
transactionId: string;
|
||||
customerName: string;
|
||||
status: string;
|
||||
channel: string;
|
||||
paymentMethod: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export const LinkPaymentItem = ({
|
||||
transactionId,
|
||||
customerName,
|
||||
status,
|
||||
channel,
|
||||
paymentMethod,
|
||||
amount
|
||||
}: LinkPaymentItemProps) => {
|
||||
const { navigate } = useNavigate();
|
||||
|
||||
const onClickToNavigate = () => {
|
||||
navigate(PATHS.additionalService.linkPayment.detail, {
|
||||
state: {
|
||||
transactionId: transactionId
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getStatusDotClass = () => {
|
||||
if (status === '결제완료' || status === '입금요청') {
|
||||
return 'status-dot blue';
|
||||
}
|
||||
return 'status-dot gray';
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="transaction-item approved"
|
||||
onClick={() => onClickToNavigate()}
|
||||
>
|
||||
<div className="transaction-status">
|
||||
<div className={getStatusDotClass()}></div>
|
||||
</div>
|
||||
<div className="transaction-content">
|
||||
<div className="transaction-title">{customerName}</div>
|
||||
<div className="transaction-details">
|
||||
<span>{status}</span>
|
||||
<span className="separator">ㅣ</span>
|
||||
<span>{channel}</span>
|
||||
<span className="separator">ㅣ</span>
|
||||
<span>{paymentMethod}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="transaction-amount">{amount.toLocaleString()}원</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { LinkPaymentDateGroup } from './link-payment-date-group';
|
||||
|
||||
interface LinkPaymentTransaction {
|
||||
transactionId: string;
|
||||
customerName: string;
|
||||
status: string;
|
||||
channel: string;
|
||||
paymentMethod: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
interface LinkPaymentListProps {
|
||||
listItems: Record<string, LinkPaymentTransaction[]>;
|
||||
}
|
||||
|
||||
export const LinkPaymentList = ({
|
||||
listItems
|
||||
}: LinkPaymentListProps) => {
|
||||
|
||||
const getListDateGroup = () => {
|
||||
let rs = [];
|
||||
for (const [key, value] of Object.entries(listItems)) {
|
||||
rs.push(
|
||||
<LinkPaymentDateGroup
|
||||
key={key}
|
||||
date={key}
|
||||
items={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return rs;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="transaction-list">
|
||||
{getListDateGroup()}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import moment from 'moment';
|
||||
import 'moment/dist/locale/ko';
|
||||
import { LinkPaymentPendingItem } from './link-payment-pending-item';
|
||||
import { JSX } from 'react';
|
||||
|
||||
interface LinkPaymentPendingTransaction {
|
||||
transactionId: string;
|
||||
customerName: string;
|
||||
status: string;
|
||||
channel: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
interface LinkPaymentPendingDateGroupProps {
|
||||
date: string;
|
||||
items: LinkPaymentPendingTransaction[];
|
||||
}
|
||||
|
||||
export const LinkPaymentPendingDateGroup = ({
|
||||
date,
|
||||
items
|
||||
}: LinkPaymentPendingDateGroupProps) => {
|
||||
moment.locale('ko');
|
||||
const getStateDate = () => {
|
||||
let stateDate = moment(date).format('YY.MM.DD(ddd)');
|
||||
return stateDate;
|
||||
};
|
||||
|
||||
const getLinkPaymentPendingItem = () => {
|
||||
const rs: JSX.Element[] = [];
|
||||
if (items && items.length > 0) {
|
||||
items.forEach((item, index) => {
|
||||
const key = 'LinkPaymentPendingItem-' + index;
|
||||
rs.push(
|
||||
<LinkPaymentPendingItem
|
||||
key={key}
|
||||
transactionId={item.transactionId}
|
||||
customerName={item.customerName}
|
||||
status={item.status}
|
||||
channel={item.channel}
|
||||
amount={item.amount}
|
||||
/>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
return rs;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="date-header">{getStateDate()}</div>
|
||||
{getLinkPaymentPendingItem()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import { PATHS } from '@/shared/constants/paths';
|
||||
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
|
||||
|
||||
interface LinkPaymentPendingItemProps {
|
||||
transactionId: string;
|
||||
customerName: string;
|
||||
status: string;
|
||||
channel: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export const LinkPaymentPendingItem = ({
|
||||
transactionId,
|
||||
customerName,
|
||||
status,
|
||||
channel,
|
||||
amount
|
||||
}: LinkPaymentPendingItemProps) => {
|
||||
const { navigate } = useNavigate();
|
||||
|
||||
const onClickToNavigate = () => {
|
||||
navigate(PATHS.additionalService.linkPayment.pendingDetail, {
|
||||
state: {
|
||||
transactionId: transactionId
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getStatusDotClass = () => {
|
||||
if (status === '발송요청') {
|
||||
return 'status-dot blue';
|
||||
}
|
||||
return 'status-dot gray';
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="transaction-item approved"
|
||||
onClick={() => onClickToNavigate()}
|
||||
>
|
||||
<div className="transaction-status">
|
||||
<div className={getStatusDotClass()}></div>
|
||||
</div>
|
||||
<div className="transaction-content">
|
||||
<div className="transaction-title">{customerName}</div>
|
||||
<div className="transaction-details">
|
||||
<span>{status}</span>
|
||||
<span className="separator">ㅣ</span>
|
||||
<span>{channel}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="transaction-amount">{amount.toLocaleString()}원</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { LinkPaymentPendingDateGroup } from './link-payment-pending-date-group';
|
||||
|
||||
interface LinkPaymentPendingTransaction {
|
||||
transactionId: string;
|
||||
customerName: string;
|
||||
status: string;
|
||||
channel: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
interface LinkPaymentPendingListProps {
|
||||
listItems: Record<string, LinkPaymentPendingTransaction[]>;
|
||||
}
|
||||
|
||||
export const LinkPaymentPendingList = ({
|
||||
listItems
|
||||
}: LinkPaymentPendingListProps) => {
|
||||
|
||||
const getListDateGroup = () => {
|
||||
let rs = [];
|
||||
for (const [key, value] of Object.entries(listItems)) {
|
||||
rs.push(
|
||||
<LinkPaymentPendingDateGroup
|
||||
key={key}
|
||||
date={key}
|
||||
items={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return rs;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="transaction-list">
|
||||
{getListDateGroup()}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,129 @@
|
||||
import { IMAGE_ROOT } from "@/shared/constants/common";
|
||||
import { useState, useEffect } from "react";
|
||||
import { LinkPaymentFilter } from "./link-payment-filter";
|
||||
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
|
||||
import { PATHS } from "@/shared/constants/paths";
|
||||
import { LinkPaymentPendingList } from "./link-payment-pending-list";
|
||||
|
||||
export const LinkPaymentPendingSendWrap = () => {
|
||||
const { navigate } = useNavigate();
|
||||
const [filterOn, setFilterOn] = useState<boolean>(false);
|
||||
const [listItems, setListItems] = useState({});
|
||||
|
||||
const onClickToOpenFilter = () => {
|
||||
setFilterOn(!filterOn);
|
||||
};
|
||||
const onClickToNavigate = () => {
|
||||
navigate(PATHS.additionalService.linkPayment.request)
|
||||
}
|
||||
|
||||
const callList = () => {
|
||||
setListItems({
|
||||
'20250608': [
|
||||
{
|
||||
transactionId: 'pending1',
|
||||
customerName: '김*환(7000)',
|
||||
status: '발송요청',
|
||||
channel: 'SMS',
|
||||
amount: 5254000
|
||||
},
|
||||
{
|
||||
transactionId: 'pending2',
|
||||
customerName: '김*환(7000)',
|
||||
status: '발송요청',
|
||||
channel: 'SMS',
|
||||
amount: 5254000
|
||||
},
|
||||
{
|
||||
transactionId: 'pending3',
|
||||
customerName: '김*환(7000)',
|
||||
status: '발송요청',
|
||||
channel: '이메일',
|
||||
amount: 5254000
|
||||
},
|
||||
{
|
||||
transactionId: 'pending4',
|
||||
customerName: '김*환(7000)',
|
||||
status: '발송취소',
|
||||
channel: 'SMS',
|
||||
amount: 5254000
|
||||
},
|
||||
{
|
||||
transactionId: 'pending5',
|
||||
customerName: '김*환(7000)',
|
||||
status: '발송취소',
|
||||
channel: 'SMS',
|
||||
amount: 5254000
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
callList();
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<section className="summary-section">
|
||||
<div className="credit-controls">
|
||||
<div>
|
||||
<input
|
||||
className="credit-period"
|
||||
type="text"
|
||||
value="2025.06.01 ~ 2025.06.30"
|
||||
readOnly={true}
|
||||
/>
|
||||
<button
|
||||
className="filter-btn"
|
||||
aria-label="필터"
|
||||
>
|
||||
<img
|
||||
src={IMAGE_ROOT + '/ico_setting.svg'}
|
||||
alt="검색옵션"
|
||||
onClick={() => onClickToOpenFilter()}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className="download-btn"
|
||||
aria-label="다운로드"
|
||||
>
|
||||
<img
|
||||
src={IMAGE_ROOT + '/ico_download.svg'}
|
||||
alt="다운로드"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="filter-section">
|
||||
<div className="sort-options">
|
||||
<button className="sort-btn active">최신순</button>
|
||||
<span className="sort-divider">|</span>
|
||||
<button className="sort-btn">고액순</button>
|
||||
</div>
|
||||
<div className="excrow">
|
||||
<div className="full-menu-keywords no-padding">
|
||||
<span className="keyword-tag active">전체</span>
|
||||
<span className="keyword-tag">성공</span>
|
||||
<span className="keyword-tag">실패</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<LinkPaymentPendingList
|
||||
listItems={listItems}
|
||||
/>
|
||||
<div className="apply-row">
|
||||
<button
|
||||
className="btn-50 btn-blue flex-1"
|
||||
onClick={() => onClickToNavigate()}
|
||||
>결제 신청</button>
|
||||
</div>
|
||||
<LinkPaymentFilter
|
||||
filterOn={filterOn}
|
||||
setFilterOn={setFilterOn}
|
||||
></LinkPaymentFilter>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { PATHS } from '@/shared/constants/paths';
|
||||
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
|
||||
import {
|
||||
LinkPaymentTabKeys,
|
||||
LinkPaymentTabProps
|
||||
} from '../../model/types'
|
||||
|
||||
export const LinkPaymentTab = ({
|
||||
activeTab
|
||||
}: LinkPaymentTabProps) => {
|
||||
const {navigate} = useNavigate();
|
||||
|
||||
const onClickToNavigation = (tab: LinkPaymentTabKeys) => {
|
||||
if(activeTab !== tab){
|
||||
if(tab === LinkPaymentTabKeys.DispatchList){
|
||||
navigate(PATHS.additionalService.linkPayment.dispatchList);
|
||||
}
|
||||
else if(tab === LinkPaymentTabKeys.PendingSend){
|
||||
navigate(PATHS.additionalService.linkPayment.pendingSend);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="subTab">
|
||||
<button
|
||||
className={`subtab-btn ${(activeTab === LinkPaymentTabKeys.DispatchList)? 'active': ''}` }
|
||||
onClick={ () => onClickToNavigation(LinkPaymentTabKeys.DispatchList) }
|
||||
>발송내역</button>
|
||||
<button
|
||||
className={`subtab-btn ${(activeTab === LinkPaymentTabKeys.PendingSend)? 'active': ''}` }
|
||||
onClick={ () => onClickToNavigation(LinkPaymentTabKeys.PendingSend) }
|
||||
>발송대기</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
SortByKeys,
|
||||
SortOptionsBoxProps
|
||||
} from '../../model/types';
|
||||
export const SortOptionsBox = ({
|
||||
sortBy,
|
||||
onClickToSort
|
||||
}: SortOptionsBoxProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="sort-options">
|
||||
<button
|
||||
className={ `sort-btn ${(sortBy === SortByKeys.New)? 'active': ''}` }
|
||||
onClick={ () => onClickToSort(SortByKeys.New) }
|
||||
>최신순</button>
|
||||
<span className="sort-divider">|</span>
|
||||
<button
|
||||
className={ `sort-btn ${(sortBy === SortByKeys.Amount)? 'active': ''}` }
|
||||
onClick={ () => onClickToSort(SortByKeys.Amount) }
|
||||
>고액순</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user