첫 커밋

This commit is contained in:
focp212@naver.com
2025-09-05 15:36:48 +09:00
commit 05238b04c1
825 changed files with 176358 additions and 0 deletions

318
src/pages/Contact.tsx Normal file
View File

@@ -0,0 +1,318 @@
import React, { useState } from 'react';
interface FormData {
name: string;
email: string;
phone: string;
subject: string;
message: string;
}
interface FormErrors {
name?: string;
email?: string;
phone?: string;
subject?: string;
message?: string;
}
const Contact: React.FC = () => {
const [formData, setFormData] = useState<FormData>({
name: '',
email: '',
phone: '',
subject: '',
message: '',
});
const [errors, setErrors] = useState<FormErrors>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const validateForm = (): boolean => {
const newErrors: FormErrors = {};
if (!formData.name.trim()) {
newErrors.name = '이름을 입력해주세요.';
}
if (!formData.email.trim()) {
newErrors.email = '이메일을 입력해주세요.';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = '올바른 이메일 형식을 입력해주세요.';
}
if (!formData.phone.trim()) {
newErrors.phone = '전화번호를 입력해주세요.';
} else if (!/^[0-9-+\s()]+$/.test(formData.phone)) {
newErrors.phone = '올바른 전화번호 형식을 입력해주세요.';
}
if (!formData.subject.trim()) {
newErrors.subject = '문의 제목을 입력해주세요.';
}
if (!formData.message.trim()) {
newErrors.message = '문의 내용을 입력해주세요.';
} else if (formData.message.length < 10) {
newErrors.message = '문의 내용을 최소 10자 이상 입력해주세요.';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value,
}));
if (errors[name as keyof FormErrors]) {
setErrors(prev => ({
...prev,
[name]: undefined,
}));
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) return;
setIsSubmitting(true);
try {
// 실제 구현에서는 API 호출
await new Promise(resolve => setTimeout(resolve, 1000));
setIsSubmitted(true);
setFormData({
name: '',
email: '',
phone: '',
subject: '',
message: '',
});
} catch (error) {
console.error('Form submission error:', error);
} finally {
setIsSubmitting(false);
}
};
if (isSubmitted) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="bg-white p-8 rounded-lg shadow-lg max-w-md w-full mx-4">
<div className="text-center">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">
</h2>
<p className="text-gray-600 mb-6">
.
</p>
<button
onClick={() => setIsSubmitted(false)}
className="bg-blue-600 text-white px-6 py-2 rounded-md hover:bg-blue-700 transition-colors"
>
</button>
</div>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4">
<div className="max-w-4xl mx-auto">
<div className="text-center mb-12">
<h1 className="text-4xl font-bold text-gray-900 mb-4">
</h1>
<p className="text-lg text-gray-600">
.
</p>
</div>
<div className="grid md:grid-cols-2 gap-12">
{/* Contact Information */}
<div>
<h2 className="text-2xl font-bold text-gray-900 mb-6">
</h2>
<div className="space-y-4">
<div className="flex items-start space-x-3">
<div className="w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
<svg className="w-3 h-3 text-blue-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
</div>
<div>
<div className="font-semibold text-gray-900"></div>
<div className="text-gray-600"> 123</div>
</div>
</div>
<div className="flex items-start space-x-3">
<div className="w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
<svg className="w-3 h-3 text-blue-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/>
</svg>
</div>
<div>
<div className="font-semibold text-gray-900"></div>
<div className="text-gray-600">contact@nicepayments.com</div>
</div>
</div>
<div className="flex items-start space-x-3">
<div className="w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
<svg className="w-3 h-3 text-blue-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M20.01 15.38c-1.23 0-2.42-.2-3.53-.56-.35-.12-.74-.03-1.01.24l-1.57 1.97c-2.83-1.35-5.48-3.9-6.89-6.83l1.95-1.66c.27-.28.35-.67.24-1.02-.37-1.11-.56-2.3-.56-3.53 0-.54-.45-.99-.99-.99H4.19C3.65 3 3 3.24 3 3.99 3 13.28 10.73 21 20.01 21c.71 0 .99-.63.99-1.18v-3.45c0-.54-.45-.99-.99-.99z"/>
</svg>
</div>
<div>
<div className="font-semibold text-gray-900"></div>
<div className="text-gray-600">02-1234-5678</div>
</div>
</div>
<div className="flex items-start space-x-3">
<div className="w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
<svg className="w-3 h-3 text-blue-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
</svg>
</div>
<div>
<div className="font-semibold text-gray-900"></div>
<div className="text-gray-600"> 09:00 - 18:00</div>
</div>
</div>
</div>
</div>
{/* Contact Form */}
<div className="bg-white p-8 rounded-lg shadow-lg">
<h2 className="text-2xl font-bold text-gray-900 mb-6">
</h2>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.name ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="이름을 입력하세요"
/>
{errors.name && <p className="mt-1 text-sm text-red-600">{errors.name}</p>}
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.email ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="이메일을 입력하세요"
/>
{errors.email && <p className="mt-1 text-sm text-red-600">{errors.email}</p>}
</div>
<div>
<label htmlFor="phone" className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<input
type="tel"
id="phone"
name="phone"
value={formData.phone}
onChange={handleChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.phone ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="전화번호를 입력하세요"
/>
{errors.phone && <p className="mt-1 text-sm text-red-600">{errors.phone}</p>}
</div>
<div>
<label htmlFor="subject" className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<select
id="subject"
name="subject"
value={formData.subject}
onChange={handleChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.subject ? 'border-red-500' : 'border-gray-300'
}`}
>
<option value=""> </option>
<option value="service"> </option>
<option value="technical"> </option>
<option value="billing">/ </option>
<option value="partnership"> </option>
<option value="other"></option>
</select>
{errors.subject && <p className="mt-1 text-sm text-red-600">{errors.subject}</p>}
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-gray-700 mb-2">
*
</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
rows={5}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.message ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="문의 내용을 자세히 입력하세요"
/>
{errors.message && <p className="mt-1 text-sm text-red-600">{errors.message}</p>}
</div>
<button
type="submit"
disabled={isSubmitting}
className="w-full bg-blue-600 text-white py-3 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{isSubmitting ? '전송 중...' : '문의 보내기'}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
);
};
export default Contact;

34
src/pages/Home.tsx Normal file
View File

@@ -0,0 +1,34 @@
import React, { useState } from 'react';
import Hero from '@/components/Hero';
import Features from '@/components/Features';
import Services from '@/components/Services';
import { BottomSheet } from '@/shared/ui/bottom-sheets/bottom-sheet';
const Home: React.FC = () => {
const [isOpenBottomSheet, setIsOpenBottomSheet] = useState(true);
return (
<div>
<Hero />
<Features />
<Services />
<BottomSheet
afterLeave={() => null}
open={isOpenBottomSheet}
onClose={() => {}}
>
<div>
<div>test</div>
<div>test</div>
<div>test</div>
<div>test</div>
<div>test</div>
<div>test</div>
<div>test</div>
<div>test</div>
</div>
</BottomSheet>
</div>
);
};
export default Home;

532
src/pages/Test.tsx Normal file
View File

@@ -0,0 +1,532 @@
import React, { useState } from 'react';
import { appBridge } from '../utils/appBridge';
import { SearchFilterExample, LanguageSwitcher } from '../components';
import { SearchFilter } from '../types/filter';
interface TestResult {
method: string;
success: boolean;
result?: any;
error?: string;
timestamp: string;
}
const Test: React.FC = () => {
const [results, setResults] = useState<TestResult[]>([]);
const [isLoading, setIsLoading] = useState<string | null>(null);
const [messageCount, setMessageCount] = useState<number>(0);
const [searchData, setSearchData] = useState<Array<{
id: string;
type: string;
amount: number;
date: Date;
status: string;
description: string;
}>>([]);
const addResult = (method: string, success: boolean, result?: any, error?: string) => {
const newResult: TestResult = {
method,
success,
result,
error,
timestamp: new Date().toLocaleTimeString()
};
setResults(prev => [newResult, ...prev]);
};
const executeTest = async (method: string, testFn: () => Promise<any>) => {
setIsLoading(method);
try {
const result = await testFn();
addResult(method, true, result);
} catch (error) {
addResult(method, false, undefined, error instanceof Error ? error.message : String(error));
} finally {
setIsLoading(null);
}
};
const clearResults = () => {
setResults([]);
};
// 앱 정보 테스트
const testGetAppInfo = () => executeTest('getAppInfo', () => appBridge.getAppInfo());
const testGetDeviceInfo = () => executeTest('getDeviceInfo', () => appBridge.getDeviceInfo());
// 네비게이션 테스트
const testNavigateBack = () => executeTest('navigateBack', () => appBridge.navigateBack());
const testNavigateTo = () => executeTest('navigateTo', () => appBridge.navigateTo('/home'));
const testCloseWebView = () => executeTest('closeWebView', () => appBridge.closeWebView());
const testNavigateToLogin = () => executeTest('navigateToLogin', () => appBridge.navigateToLogin());
// 알림 테스트
const testShowToast = () => executeTest('showToast', () => appBridge.showToast('테스트 토스트 메시지', 3000));
const testShowAlert = () => executeTest('showAlert', () => appBridge.showAlert('테스트 알림', '이것은 테스트 알림입니다.'));
const testShowConfirm = () => executeTest('showConfirm', () => appBridge.showConfirm('확인', '계속하시겠습니까?'));
// 저장소 테스트
const testSetStorage = () => executeTest('setStorage', () => appBridge.setStorage('test_key', { message: '테스트 데이터', timestamp: new Date().toISOString() }));
const testGetStorage = () => executeTest('getStorage', () => appBridge.getStorage('test_key'));
const testRemoveStorage = () => executeTest('removeStorage', () => appBridge.removeStorage('test_key'));
// 공유 테스트
const testShareContent = () => executeTest('shareContent', () => appBridge.shareContent({
title: '테스트 공유',
text: '이것은 테스트 공유 내용입니다.',
url: 'https://nicepayments.com'
}));
// 인증 테스트
const testLogin = () => executeTest('login', () => appBridge.login({ email: 'test@example.com', password: 'test123' }));
const testLogout = () => executeTest('logout', () => appBridge.logout());
// 안전한 호출 테스트
const testSafeCall = () => executeTest('safeCall', () => appBridge.safeCall(
() => appBridge.getAppInfo(),
{ version: 'fallback', buildNumber: 'fallback' },
(error) => console.error('Safe call error:', error)
));
// 타임아웃 테스트
const testCallWithTimeout = () => executeTest('callWithTimeout', () => appBridge.callWithTimeout(
() => appBridge.getDeviceInfo(),
2000
));
// 메시지 카운트 업데이트 테스트
const testUpdateMessageCount = () => executeTest('updateMessageCount', () => appBridge.updateMessageCount(messageCount));
// 조회조건 변경 핸들러
const handleFilterChange = (filter: SearchFilter) => {
addResult('filterChange', true, filter);
// 가상 데이터로 검색 결과 시뮬레이션
const mockData: any = generateMockData(filter);
setSearchData(mockData);
};
// 가상 거래 데이터 생성
const generateMockData = (filter: SearchFilter) => {
const transactionTypes = ['deposit', 'withdrawal'];
const data = [];
for (let i = 0; i < 10; i++) {
const type = filter.transactionType === 'all'
? transactionTypes[Math.floor(Math.random() * transactionTypes.length)]
: filter.transactionType;
const amount = Math.floor(Math.random() * 1000000) + 10000;
const date = new Date();
date.setDate(date.getDate() - Math.floor(Math.random() * 180));
data.push({
id: `TXN${String(i + 1).padStart(3, '0')}`,
type: type,
amount: amount,
date: date,
status: Math.random() > 0.1 ? 'completed' : 'failed',
description: type === 'deposit' ? '입금' : '출금'
});
}
// 정렬 적용
data.sort((a, b) => {
if (filter.sortOrder === 'latest') {
return b.date.getTime() - a.date.getTime();
} else {
return a.date.getTime() - b.date.getTime();
}
});
return data;
};
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="max-w-6xl mx-auto px-4">
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
<div className="flex items-center justify-between mb-6">
<h1 className="text-3xl font-bold text-gray-900">AppBridge </h1>
<div className="flex space-x-4">
<LanguageSwitcher />
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
appBridge.isNativeEnvironment()
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}>
{appBridge.isNativeEnvironment() ? '네이티브 환경' : '웹 환경'}
</span>
{appBridge.isNativeEnvironment() && (
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
appBridge.isAndroid()
? 'bg-blue-100 text-blue-800'
: 'bg-gray-100 text-gray-800'
}`}>
{appBridge.isAndroid() ? 'Android' : appBridge.isIOS() ? 'iOS' : 'Unknown'}
</span>
)}
</div>
</div>
{!appBridge.isNativeEnvironment() && (
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-4 mb-6">
<div className="flex">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-yellow-800">
</h3>
<div className="mt-2 text-sm text-yellow-700">
<p> AppBridge . .</p>
</div>
</div>
</div>
</div>
)}
</div>
{/* 조회조건 테스트 섹션 */}
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4"> </h2>
<SearchFilterExample onFilterChange={handleFilterChange} />
</div>
{/* 검색 결과 표시 */}
{searchData.length > 0 && (
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4">
({searchData.length})
</h2>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
ID
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{searchData.map((item, index) => (
<tr key={index} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{item.id}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
item.type === 'deposit'
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}>
{item.description}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{item.amount.toLocaleString()}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{item.date.toLocaleDateString('ko-KR')}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
item.status === 'completed'
? 'bg-blue-100 text-blue-800'
: 'bg-gray-100 text-gray-800'
}`}>
{item.status === 'completed' ? '완료' : '실패'}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 테스트 버튼들 */}
<div className="bg-white rounded-lg shadow-lg p-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4"> </h2>
{/* 앱 정보 */}
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-800 mb-2"> </h3>
<div className="grid grid-cols-2 gap-2">
<button
onClick={testGetAppInfo}
disabled={isLoading === 'getAppInfo'}
className="bg-blue-500 hover:bg-blue-600 disabled:bg-blue-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'getAppInfo' ? '실행 중...' : '앱 정보'}
</button>
<button
onClick={testGetDeviceInfo}
disabled={isLoading === 'getDeviceInfo'}
className="bg-blue-500 hover:bg-blue-600 disabled:bg-blue-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'getDeviceInfo' ? '실행 중...' : '디바이스 정보'}
</button>
</div>
</div>
{/* 네비게이션 */}
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-800 mb-2"></h3>
<div className="grid grid-cols-2 gap-2">
<button
onClick={testNavigateBack}
disabled={isLoading === 'navigateBack'}
className="bg-green-500 hover:bg-green-600 disabled:bg-green-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'navigateBack' ? '실행 중...' : '뒤로 가기'}
</button>
<button
onClick={testNavigateTo}
disabled={isLoading === 'navigateTo'}
className="bg-green-500 hover:bg-green-600 disabled:bg-green-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'navigateTo' ? '실행 중...' : '페이지 이동'}
</button>
<button
onClick={testNavigateToLogin}
disabled={isLoading === 'navigateToLogin'}
className="bg-green-500 hover:bg-green-600 disabled:bg-green-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'navigateTo' ? '실행 중...' : '로그인 화면'}
</button>
<button
onClick={testCloseWebView}
disabled={isLoading === 'closeWebView'}
className="bg-red-500 hover:bg-red-600 disabled:bg-red-300 text-white px-4 py-2 rounded text-sm col-span-2"
>
{isLoading === 'closeWebView' ? '실행 중...' : '웹뷰 닫기'}
</button>
</div>
</div>
{/* 알림 */}
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-800 mb-2"></h3>
<div className="grid grid-cols-2 gap-2">
<button
onClick={testShowToast}
disabled={isLoading === 'showToast'}
className="bg-yellow-500 hover:bg-yellow-600 disabled:bg-yellow-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'showToast' ? '실행 중...' : '토스트'}
</button>
<button
onClick={testShowAlert}
disabled={isLoading === 'showAlert'}
className="bg-yellow-500 hover:bg-yellow-600 disabled:bg-yellow-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'showAlert' ? '실행 중...' : '알림'}
</button>
<button
onClick={testShowConfirm}
disabled={isLoading === 'showConfirm'}
className="bg-yellow-500 hover:bg-yellow-600 disabled:bg-yellow-300 text-white px-4 py-2 rounded text-sm col-span-2"
>
{isLoading === 'showConfirm' ? '실행 중...' : '확인 대화상자'}
</button>
</div>
</div>
{/* 저장소 */}
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-800 mb-2"></h3>
<div className="grid grid-cols-3 gap-2">
<button
onClick={testSetStorage}
disabled={isLoading === 'setStorage'}
className="bg-purple-500 hover:bg-purple-600 disabled:bg-purple-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'setStorage' ? '실행 중...' : '저장'}
</button>
<button
onClick={testGetStorage}
disabled={isLoading === 'getStorage'}
className="bg-purple-500 hover:bg-purple-600 disabled:bg-purple-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'getStorage' ? '실행 중...' : '읽기'}
</button>
<button
onClick={testRemoveStorage}
disabled={isLoading === 'removeStorage'}
className="bg-purple-500 hover:bg-purple-600 disabled:bg-purple-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'removeStorage' ? '실행 중...' : '삭제'}
</button>
</div>
</div>
{/* 결제 및 인증 */}
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-800 mb-2"> & </h3>
<div className="space-y-2">
<div className="grid grid-cols-2 gap-2">
<button
onClick={testLogin}
disabled={isLoading === 'login'}
className="bg-green-600 hover:bg-green-700 disabled:bg-green-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'login' ? '실행 중...' : '로그인'}
</button>
<button
onClick={testLogout}
disabled={isLoading === 'logout'}
className="bg-red-600 hover:bg-red-700 disabled:bg-red-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'logout' ? '실행 중...' : '로그아웃'}
</button>
</div>
</div>
</div>
{/* 공유 */}
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-800 mb-2"></h3>
<div className="space-y-2">
<button
onClick={testShareContent}
disabled={isLoading === 'shareContent'}
className="bg-cyan-500 hover:bg-cyan-600 disabled:bg-cyan-300 text-white px-4 py-2 rounded text-sm w-full"
>
{isLoading === 'shareContent' ? '실행 중...' : '공유'}
</button>
</div>
</div>
{/* 메시지 카운트 */}
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-800 mb-2"> </h3>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<input
type="number"
value={messageCount}
onChange={(e) => setMessageCount(parseInt(e.target.value) || 0)}
min="0"
className="border border-gray-300 rounded px-3 py-1 text-sm w-20"
placeholder="0"
/>
<button
onClick={testUpdateMessageCount}
disabled={isLoading === 'updateMessageCount'}
className="bg-orange-500 hover:bg-orange-600 disabled:bg-orange-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'updateMessageCount' ? '실행 중...' : '배지 업데이트'}
</button>
</div>
<p className="text-xs text-gray-600">
iOS: / Android: 로그
</p>
</div>
</div>
{/* 고급 테스트 */}
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-800 mb-2"> </h3>
<div className="grid grid-cols-2 gap-2">
<button
onClick={testSafeCall}
disabled={isLoading === 'safeCall'}
className="bg-gray-500 hover:bg-gray-600 disabled:bg-gray-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'safeCall' ? '실행 중...' : '안전한 호출'}
</button>
<button
onClick={testCallWithTimeout}
disabled={isLoading === 'callWithTimeout'}
className="bg-gray-500 hover:bg-gray-600 disabled:bg-gray-300 text-white px-4 py-2 rounded text-sm"
>
{isLoading === 'callWithTimeout' ? '실행 중...' : '타임아웃 호출'}
</button>
</div>
</div>
</div>
{/* 테스트 결과 */}
<div className="bg-white rounded-lg shadow-lg p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold text-gray-900"> </h2>
<button
onClick={clearResults}
className="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded text-sm"
>
</button>
</div>
<div className="space-y-3 max-h-96 overflow-y-auto">
{results.length === 0 ? (
<p className="text-gray-500 text-center py-8">
.
</p>
) : (
results.map((result, index) => (
<div
key={index}
className={`p-3 rounded-lg border ${
result.success
? 'bg-green-50 border-green-200'
: 'bg-red-50 border-red-200'
}`}
>
<div className="flex items-center justify-between">
<span className="font-medium text-gray-900">
{result.method}
</span>
<span className="text-sm text-gray-500">
{result.timestamp}
</span>
</div>
{result.success ? (
<div className="mt-2">
<span className="text-green-800 text-sm"> </span>
{result.result && (
<pre className="mt-1 text-xs bg-green-100 p-2 rounded overflow-x-auto">
{JSON.stringify(result.result, null, 2)}
</pre>
)}
</div>
) : (
<div className="mt-2">
<span className="text-red-800 text-sm"> </span>
<p className="mt-1 text-xs text-red-700 bg-red-100 p-2 rounded">
{result.error}
</p>
</div>
)}
</div>
))
)}
</div>
</div>
</div>
</div>
</div>
);
};
export default Test;

View File

@@ -0,0 +1,31 @@
import { Route } from 'react-router-dom';
import { SentryRoutes } from '@/shared/configs/sentry';
import { ROUTE_NAMES } from '@/shared/constants/route-names';
import { UserManagePage } from './user/manage-page';
import { UserLoginAuthInfoPage } from './user/login-auth-info-page';
import { UserAccountAuthPage } from './user/account-auth-page';
import { UserMenuAuthPage } from './user/menu-auth-page';
import { UserAddAccountPage } from './user/add-account-page';
import { PasswordManagePage } from './password/manage-page';
import { PasswordModifyLoginPasswordPage } from './password/modify-login-password-page';
export const AccountPages = () => {
return (
<>
<SentryRoutes>
<Route path={ROUTE_NAMES.account.user.base}>
<Route path={ROUTE_NAMES.account.user.manage} element={<UserManagePage />} />
<Route path={ROUTE_NAMES.account.user.loginAuthInfo} element={<UserLoginAuthInfoPage />} />
<Route path={ROUTE_NAMES.account.user.accountAuth} element={<UserAccountAuthPage />} />
<Route path={ROUTE_NAMES.account.user.menuAuth} element={<UserMenuAuthPage />} />
<Route path={ROUTE_NAMES.account.user.addAccount} element={<UserAddAccountPage />} />
</Route>
<Route path={ROUTE_NAMES.account.password.base}>
<Route path={ROUTE_NAMES.account.password.manage} element={<PasswordManagePage />} />
<Route path={ROUTE_NAMES.account.password.modifyLoginPassword} element={<PasswordModifyLoginPasswordPage />} />
</Route>
</SentryRoutes>
</>
);
};

View File

@@ -0,0 +1,38 @@
import { useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { AccountTab } from '@/entities/account/ui/account-tab';
import { PasswordManageWrap } from '@/entities/account/ui/password-manage-wrap';
import { AccountTabKeys } from '@/entities/account/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const PasswordManagePage = () => {
const { navigate } = useNavigate();
const [activeTab, setActiveTab] = useState<AccountTabKeys>(AccountTabKeys.PasswordManage);
useSetHeaderTitle('계정 관리');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
useSetOnBack(() => {
navigate(PATHS.home);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<AccountTab activeTab={ activeTab }></AccountTab>
<PasswordManageWrap></PasswordManageWrap>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,72 @@
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const PasswordModifyLoginPasswordPage = () => {
const { navigate } = useNavigate();
useSetHeaderTitle('로그인 비밀번호 변경');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false);
useSetOnBack(() => {
navigate(PATHS.account.password.manage);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<div className="ing-list add">
<div className="user-add">
<div className="ua-row">
<div className="ua-label"> <span className="red">*</span></div>
<select className="wid-100">
<option>nictest01g</option>
</select>
</div>
<div className="ua-row">
<div className="ua-label"> <span className="red">*</span></div>
<input
className="wid-100"
type="password"
placeholder=""
/>
</div>
<div className="ua-row">
<div className="ua-label"> <span className="red">*</span></div>
<input
className="wid-100 error"
type="password"
placeholder=""
/>
</div>
<div className="ua-help error"> </div>
<div className="ua-row">
<div className="ua-label"> <span className="red">*</span></div>
<input
className="wid-100 error"
type="password"
placeholder=""
/>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
type="button"
></button>
</div>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,50 @@
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { AccountUserTab } from '@/entities/account/ui/account-user-tab';
import { UserAccountAuthWrap } from '@/entities/account/ui/user-account-auth-wrap';
import { AccountUserTabKeys } from '@/entities/account/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const UserAccountAuthPage = () => {
const { navigate } = useNavigate();
const location = useLocation();
const [tid, setTid] = useState<string>(location?.state.tid);
const [activeTab, setActiveTab] = useState<AccountUserTabKeys>(AccountUserTabKeys.AccountAuth);
useSetHeaderTitle('사용자 설정');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
useSetOnBack(() => {
navigate(PATHS.account.user.manage);
});
useEffect(() => {
console.log('tid : ', tid);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<AccountUserTab
activeTab={ activeTab }
tid={ tid }
></AccountUserTab>
<UserAccountAuthWrap
tid={ tid }
></UserAccountAuthWrap>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,120 @@
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const UserAddAccountPage = () => {
const { navigate } = useNavigate();
useSetHeaderTitle('사용자 추가');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false);
useSetOnBack(() => {
navigate(PATHS.account.user.manage);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<div className="ing-list add">
<div className="user-add">
<div className="ua-row">
<div className="ua-label">ID <span className="red">*</span></div>
<input
className="wid-100 error"
type="text"
placeholder="ID를 입력해 주세요"
/>
</div>
<div className="ua-help error"> ID가 .</div>
<div className="ua-row">
<div className="ua-label"> <span className="red">*</span></div>
<input
className="wid-100 error"
type="password"
placeholder="8자리 이상 입력해 주세요"
/>
</div>
<div className="ua-help error">( )</div>
<div className="ua-row">
<div className="ua-label"> </div>
<select className="wid-100">
<option>MID + GID</option>
</select>
</div>
</div>
<div className="info-divider"></div>
<div className="user-add info">
<div className="ua-desc">
<div className="ua-title"> </div>
<p className="ua-note"> .</p>
</div>
<div className="ua-group">
<div className="ua-group-header">
<div className="ua-group-title"> </div>
<button
className="ic20 plus"
type="button"
aria-label="이메일 추가"
></button>
</div>
<div className="ua-input-row">
<input
className="wid-100"
type="text"
placeholder="example@domain.com"
/>
<button
className="icon-btn minus"
type="button"
aria-label="삭제"
></button>
</div>
</div>
<div className="ua-group">
<div className="ua-group-header">
<div className="ua-group-title"> </div>
<button
className="ic20 plus"
type="button"
aria-label="휴대폰 추가"
></button>
</div>
<div className="ua-input-row">
<input
className="wid-100"
type="text"
placeholder="01012345678"
/>
<button
className="icon-btn minus"
type="button"
aria-label="삭제"
></button>
</div>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
type="button"
></button>
</div>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,50 @@
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { AccountUserTab } from '@/entities/account/ui/account-user-tab';
import { UserLoginAuthInfoWrap } from '@/entities/account/ui/user-login-auth-info-wrap';
import { AccountUserTabKeys } from '@/entities/account/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const UserLoginAuthInfoPage = () => {
const { navigate } = useNavigate();
const location = useLocation();
const [tid, setTid] = useState<string>(location?.state.tid);
const [activeTab, setActiveTab] = useState<AccountUserTabKeys>(AccountUserTabKeys.LoginAuthInfo);
useSetHeaderTitle('사용자 설정');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
useSetOnBack(() => {
navigate(PATHS.account.user.manage);
});
useEffect(() => {
console.log('tid : ', tid);
}, []);
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<AccountUserTab
activeTab={ activeTab }
tid={ tid }
></AccountUserTab>
<UserLoginAuthInfoWrap
tid={ tid }
></UserLoginAuthInfoWrap>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,41 @@
import { useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { AccountTab } from '@/entities/account/ui/account-tab';
import { UserManageWrap } from '@/entities/account/ui/user-manage-wrap';
import { AccountTabKeys } from '@/entities/account/model/types';
import { FooterItemActiveKey } from '@/entities/common/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetFooterCurrentPage,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const UserManagePage = () => {
const { navigate } = useNavigate();
const [activeTab, setActiveTab] = useState<AccountTabKeys>(AccountTabKeys.UserManage);
useSetHeaderTitle('계정 관리');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
useSetFooterCurrentPage(FooterItemActiveKey.Account);
useSetOnBack(() => {
navigate(PATHS.home);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<AccountTab activeTab={ activeTab }></AccountTab>
<UserManageWrap></UserManageWrap>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,134 @@
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const UserMenuAuthPage = () => {
const { navigate } = useNavigate();
const location = useLocation();
const [tid, setTid] = useState<string>(location?.state.tid);
const [menuId, setMenuId] = useState<string>(location?.state.menuId);
useSetHeaderTitle('사용자 설정');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
useSetOnBack(() => {
navigate(PATHS.account.user.accountAuth, {
state: {
tid: tid
}
});
});
useEffect(() => {
console.log('tid : ', tid);
console.log('menuId : ', menuId);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<div className="ing-list sev">
<div className="desc service-tip"> .</div>
<div className="desc service-tip"> .</div>
<div className="settings-section nopadding">
<div className="settings-row">
<div className="settings-row-title bd-style"> </div>
<label className="settings-switch">
<input
type="checkbox"
checked
/>
<span className="slider"></span>
</label>
</div>
<div className="set-divider"></div>
<div className="settings-row">
<span className="settings-row-title bd-sub dot"></span>
<label className="settings-switch">
<input
type="checkbox"
checked
/>
<span className="slider"></span>
</label>
</div>
<div className="settings-row">
<span className="settings-row-title bd-sub dot"></span>
<label className="settings-switch">
<input
type="checkbox"
checked
/>
<span className="slider"></span>
</label>
</div>
<div className="settings-row">
<span className="settings-row-title bd-sub dot"></span>
<label className="settings-switch">
<input
type="checkbox"
checked
/>
<span className="slider"></span>
</label>
</div>
<div className="settings-row">
<span className="settings-row-title bd-sub dot"></span>
<label className="settings-switch">
<input
type="checkbox"
checked
/>
<span className="slider"></span>
</label>
</div>
</div>
<div className="ht-20"></div>
<div className="settings-section">
<div className="settings-row">
<div className="settings-row-title bd-style"> </div>
<label className="settings-switch">
<input type="checkbox" />
<span className="slider"></span>
</label>
</div>
<div className="settings-row">
<div className="settings-row-title bd-style"></div>
<label className="settings-switch">
<input type="checkbox" />
<span className="slider"></span>
</label>
</div>
<div className="settings-row">
<div className="settings-row-title bd-style"></div>
<label className="settings-switch">
<input type="checkbox" />
<span className="slider"></span>
</label>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
type="button"
></button>
</div>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,18 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const AccountHolderAuthPage = () => {
useSetHeaderTitle('계좌점유인증');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
return (
<>
</>
);
};

View File

@@ -0,0 +1,18 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const AccountHolderSearchPage = () => {
useSetHeaderTitle('계좌성명조회');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
return (
<>
</>
);
};

View File

@@ -0,0 +1,41 @@
import { Route } from 'react-router-dom';
import { SentryRoutes } from '@/shared/configs/sentry';
import { ROUTE_NAMES } from '@/shared/constants/route-names';
import { IntroPage } from './intro/intro-page';
import { ArsCardPaymentListPage } from './ars-card-payment/list-page';
import { ArsCardPaymentRequestPage } from './ars-card-payment/request-page';
import { ArsCardPaymentRequestSuccessPage } from './ars-card-payment/request-success-page';
import { KeyInPaymentPage } from './key-in-payment/key-in-payment-page';
import { SmsPaymentNotificationPage } from './sms-payment-notification/sms-payment-notification-page';
import { AccountHolderSearchPage } from './account-holder-search/account-holder-search-page';
import { AccountHolderAuthPage } from './account-holder-auth/account-holder-auth-page';
import { LinkPaymentPage } from './link-payment/link-payment-page';
import { KakaoPaymentNotificationPage } from './kakao-payment-notification/kakao-payment-notification-page';
import { FundTransferPage } from './fund-transfer/fund-transfer-page';
import { SettlementAgencyPage } from './settlement-agency/settlement-agency-page';
import { PaymentAgencyPage } from './payment-agency/payment-agency-page';
export const AdditionalServicePages = () => {
return (
<>
<SentryRoutes>
<Route path={ROUTE_NAMES.additionalService.intro} element={<IntroPage />} />
<Route path={ROUTE_NAMES.additionalService.arsCardPayment.base}>
<Route path={ROUTE_NAMES.additionalService.arsCardPayment.list} element={<ArsCardPaymentListPage />} />
<Route path={ROUTE_NAMES.additionalService.arsCardPayment.request} element={<ArsCardPaymentRequestPage />} />
<Route path={ROUTE_NAMES.additionalService.arsCardPayment.requestSuccess} element={<ArsCardPaymentRequestSuccessPage />} />
</Route>
<Route path={ROUTE_NAMES.additionalService.keyInPayment} element={<KeyInPaymentPage />} />
<Route path={ROUTE_NAMES.additionalService.smsPaymentNotification} element={<SmsPaymentNotificationPage />} />
<Route path={ROUTE_NAMES.additionalService.accountHolderSearch} element={<AccountHolderSearchPage />} />
<Route path={ROUTE_NAMES.additionalService.accountHolderAuth} element={<AccountHolderAuthPage />} />
<Route path={ROUTE_NAMES.additionalService.linkPayment} element={<LinkPaymentPage />} />
<Route path={ROUTE_NAMES.additionalService.kakaoPaymentNotification} element={<KakaoPaymentNotificationPage />} />
<Route path={ROUTE_NAMES.additionalService.fundTransfer} element={<FundTransferPage />} />
<Route path={ROUTE_NAMES.additionalService.settlementAgency} element={<SettlementAgencyPage />} />
<Route path={ROUTE_NAMES.additionalService.paymentAgency} element={<PaymentAgencyPage />} />
</SentryRoutes>
</>
);
};

View File

@@ -0,0 +1,217 @@
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const ArsCardPaymentListPage = () => {
const { navigate } = useNavigate();
useSetHeaderTitle('신용카드 ARS 결제');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false);
useSetOnBack(() => {
navigate(PATHS.home);
});
const onClickToNavigation = () => {
navigate(PATHS.additionalService.arsCardPayment.request);
};
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<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="검색옵션"
/>
</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>
<section className="transaction-list">
<div className="date-group">
<div className="date-header">25.06.08()</div>
<div className="transaction-item approved">
<div className="transaction-status">
<div className="status-dot blue"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">*(7000)</div>
<div className="transaction-details">
<span>20:00ㅣ미결제ㅣ결제대기ㅣSMS</span>
</div>
</div>
<div className="transaction-amount">5,254,000</div>
</div>
<div className="transaction-item refund">
<div className="transaction-status">
<div className="status-dot gray"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">*(7000)</div>
<div className="transaction-details">
<span>20:00ㅣ결제완료ㅣ결제성공ㅣ호전환</span>
</div>
</div>
<div className="transaction-amount">23,845,000</div>
</div>
<div className="transaction-item approved">
<div className="transaction-status">
<div className="status-dot blue"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">*(7000)</div>
<div className="transaction-details">
<span>20:00ㅣ결제완료ㅣ결제성공ㅣ호전환</span>
</div>
</div>
<div className="transaction-amount">534,000</div>
</div>
<div className="transaction-item refund">
<div className="transaction-status">
<div className="status-dot gray"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">*(7000)</div>
<div className="transaction-details">
<span>20:00ㅣ미결제ㅣ취소완료ㅣSMS</span>
</div>
</div>
<div className="transaction-amount">4,254,000</div>
</div>
<div className="transaction-item approved">
<div className="transaction-status">
<div className="status-dot blue"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">*(7000)</div>
<div className="transaction-details">
<span>20:00ㅣ미결제ㅣ기간만료ㅣSMS</span>
</div>
</div>
<div className="transaction-amount">948,000</div>
</div>
</div>
<div className="date-group">
<div className="date-header">25.06.08()</div>
<div className="transaction-item approved">
<div className="transaction-status">
<div className="status-dot blue"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">(, )</div>
<div className="transaction-details">
<span>승인ㅣ20:00ㅣcroquis01m</span>
</div>
</div>
<div className="transaction-amount">3,583,000</div>
</div>
<div className="transaction-item refund">
<div className="transaction-status">
<div className="status-dot gray"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">(., )</div>
<div className="transaction-details">
<span>환불ㅣ20:00ㅣcroquis01m</span>
</div>
</div>
<div className="transaction-amount">874,000</div>
</div>
<div className="transaction-item approved">
<div className="transaction-status">
<div className="status-dot blue"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">(, PAYU)</div>
<div className="transaction-details">
<span>승인ㅣ20:00ㅣcroquis01m</span>
</div>
</div>
<div className="transaction-amount">23,562,000</div>
</div>
<div className="transaction-item refund">
<div className="transaction-status">
<div className="status-dot gray"></div>
</div>
<div className="transaction-content">
<div className="transaction-title"></div>
<div className="transaction-details">
<span>환불ㅣ20:00ㅣcroquis01m</span>
</div>
</div>
<div className="transaction-amount">783,000</div>
</div>
<div className="transaction-item approved">
<div className="transaction-status">
<div className="status-dot blue"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">SSG ()</div>
<div className="transaction-details">
<span>승인ㅣ20:00ㅣcroquis01m</span>
</div>
</div>
<div className="transaction-amount">3,923,000</div>
</div>
</div>
</section>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={ () => onClickToNavigation() }
> </button>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,163 @@
import { useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { useExtensionArsApplyMutation } from '@/entities/additional-service/api/use-extension-ars-apply-mutation';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const ArsCardPaymentRequestPage = () => {
const { navigate } = useNavigate();
const { mutateAsync: arsApply } = useExtensionArsApplyMutation();
useSetHeaderTitle('결제 신청');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false);
useSetOnBack(() => {
navigate(PATHS.additionalService.arsCardPayment.list);
});
const callArsCardPaymentRequest = () => {
let arsApplyParams = {
mid: 'string',
moid: 'string',
goodsName: 'string',
amount: 0,
instmntMonth: '00',
buyerName: 'string',
phoneNumber: 'string',
email: 'string',
arsPaymentMethod: 'SMS',
};
arsApply(arsApplyParams).then((rs) => {
navigate(PATHS.additionalService.arsCardPayment.requestSuccess);
console.log(rs)
}).catch(() => {
}).finally(() => {
});
};
const onClickToRequest = () => {
callArsCardPaymentRequest();
};
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<div className="option-list">
<div className="billing-form gap-16">
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value="nictest001m"
readOnly={ true }
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value="wadizcop0g2025062"
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value="123456"
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value="1000"
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<select disabled>
<option selected></option>
<option></option>
</select>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value="김테스트"
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value="01012345678"
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"></div>
<div className="billing-field">
<input
type="text"
value="NICE@NAVER.COM"
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<div className="seg-buttons">
<button className="btn-36 btn-blue light">SMS</button>
<button className="btn-36 btn-white light"></button>
</div>
</div>
</div>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={ () => onClickToRequest() }
> </button>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,43 @@
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderType,
useSetFooterMode,
} from '@/widgets/sub-layout/use-sub-layout';
export const ArsCardPaymentRequestSuccessPage = () => {
const { navigate } = useNavigate();
useSetHeaderType(HeaderType.NoHeader);
useSetFooterMode(false);
const onClickToNavigate = () => {
navigate(PATHS.additionalService.arsCardPayment.list);
};
return (
<>
<div className="success-page" >
<div className="success-body">
<div
className="success-icon"
aria-hidden="true"
></div>
<h1 className="success-title">
<span> ARS</span><br/>
<span> .</span>
</h1>
<div className="success-result">
<p className="result-text"> : [0000] ARS </p>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={ () => onClickToNavigate() }
></button>
</div>
</div>
</>
);
};

View File

@@ -0,0 +1,18 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const FundTransferPage = () => {
useSetHeaderTitle('자금이체');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
return (
<>
</>
);
};

View File

@@ -0,0 +1,124 @@
import { PATHS } from '@/shared/constants/paths';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { IntroListItem } from '@/entities/additional-service/ui/intro-list-item';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const IntroPage = () => {
const { navigate } = useNavigate();
useSetHeaderTitle('부가서비스 소개');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
useSetOnBack(() => {
navigate(PATHS.home);
});
const usingList = [
{
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.arsCardPayment.list
},
{
className: 'list-wrap01', serviceName: 'KEY-IN 결제', serviceDesc: '상담 중 카드정보 입력으로 간편한 결제 지원',
icon: IMAGE_ROOT + '/icon_ing02.svg', path: PATHS.additionalService.keyInPayment
},
{
className: 'list-wrap01', serviceName: '계좌성명조회', serviceDesc: '예금주 정보 입력으로 즉시 예금주 확인',
icon: IMAGE_ROOT + '/icon_ing04.svg', path: PATHS.additionalService.accountHolderSearch
},
];
const requestList = [
{
className: 'list-wrap02', serviceName: '지급대행', serviceDesc: '하위 가맹점에 빠른 정산금 지급 지급대행 서비스',
icon: IMAGE_ROOT + '/icon_ing05.svg', path: PATHS.additionalService.paymentAgency
},
{
className: 'list-wrap02', serviceName: '정산대행', serviceDesc: '하위 가맹점 정산금 계산부터 지급까지 자동 해결 서비스',
icon: IMAGE_ROOT + '/icon_ing06.svg', path: PATHS.additionalService.settlementAgency
},
{
className: 'list-wrap02', serviceName: '링크 결제', serviceDesc: '결제 링크 전송만으로 어디서든 결제 가능 서비스',
icon: IMAGE_ROOT + '/icon_ing07.svg', path: PATHS.additionalService.linkPayment
},
{
className: 'list-wrap02', serviceName: '자금이체', serviceDesc: '예치금으로 즉시 송금, 파일 등록만으로 다중 송금 가능',
icon: IMAGE_ROOT + '/icon_ing08.svg', path: PATHS.additionalService.fundTransfer
},
{
className: 'list-wrap02', serviceName: '계좌점유인증', serviceDesc: '1원 송금으로 실제 계좌 점유 확인 여부',
icon: IMAGE_ROOT + '/icon_ing09.svg', path: PATHS.additionalService.accountHolderAuth
},
{
className: 'list-wrap02', serviceName: '알림톡 결제통보', serviceDesc: '결제 상태를 알림톡으로 쉽고 빠른 안내',
icon: IMAGE_ROOT + '/icon_ing10.svg', path: PATHS.additionalService.kakaoPaymentNotification
},
];
const getUsingList = () => {
let rs = [];
for(let i=0;i<usingList.length;i++){
rs.push(
<IntroListItem
key={ 'key-using-' + i }
className={ usingList[i]?.className }
serviceName={ usingList[i]?.serviceName }
serviceDesc={ usingList[i]?.serviceDesc }
icon={ usingList[i]?.icon }
path={ usingList[i]?.path }
></IntroListItem>
);
}
return rs;
};
const getRequestList = () => {
let rs = [];
for(let i=0;i<requestList.length;i++){
rs.push(
<IntroListItem
key={ 'key-request-' + i }
className={ requestList[i]?.className }
serviceName={ requestList[i]?.serviceName }
serviceDesc={ requestList[i]?.serviceDesc }
icon={ requestList[i]?.icon }
path={ requestList[i]?.path }
></IntroListItem>
);
}
return rs;
};
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<div className="ing-list">
<div className="input-wrapper top-select">
<select>
<option value="1">nicetest00g</option>
<option value="2">nicetest00g</option>
<option value="3">nicetest00g</option>
</select>
</div>
<h3 className="ing-title">사용중인 서비스</h3>
{ getUsingList() }
<h3 className="ing-title">신청 가능한 서비스</h3>
{ getRequestList() }
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,18 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const KakaoPaymentNotificationPage = () => {
useSetHeaderTitle('알림톡 결제통보');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
return (
<>
</>
);
};

View File

@@ -0,0 +1,18 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const KeyInPaymentPage = () => {
useSetHeaderTitle('KEY-IN 결제');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
return (
<>
</>
);
};

View File

@@ -0,0 +1,18 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const LinkPaymentPage = () => {
useSetHeaderTitle('링크결제');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
return (
<>
</>
);
};

View File

@@ -0,0 +1,18 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const PaymentAgencyPage = () => {
useSetHeaderTitle('지급대행');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
return (
<>
</>
);
};

View File

@@ -0,0 +1,18 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const SettlementAgencyPage = () => {
useSetHeaderTitle('정산대행');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
return (
<>
</>
);
};

View File

@@ -0,0 +1,149 @@
import { useState } from 'react';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { SmsPaymentDetailResend } from '@/entities/additional-service/ui/sms-payment-detail-resend';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const SmsPaymentNotificationPage = () => {
const [bottomSmsPaymentDetailResendOn, setBottomSmsPaymentDetailResendOn] = useState<boolean>(false)
useSetHeaderTitle('SMS 결제 통보');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
const onClickToShowDetail = () => {
setBottomSmsPaymentDetailResendOn(true);
};
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<section className="summary-section no-border">
<div className="credit-controls">
<div>
<input
className="credit-period"
type="text"
value="2025.06.01 ~ 2025.06.31"
readOnly={ true }
/>
<button
className="filter-btn"
aria-label="필터"
>
<img
src={ IMAGE_ROOT + '/ico_setting.svg' }
alt="검색옵션"
/>
</button>
</div>
<button
className="download-btn"
aria-label="다운로드"
>
<img
src={ IMAGE_ROOT + '/ico_download.svg' }
alt="다운로드"
/>
</button>
</div>
</section>
<section className="transaction-list">
<div className="date-group">
<div className="date-header">25.06.08()</div>
<div
className="transaction-item approved"
onClick={ () => onClickToShowDetail() }
>
<div className="transaction-status">
<div className="status-dot blue"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">*(7000)</div>
<div className="transaction-details">
<span>nictest01m</span>
<span className="separator"></span>
<span> </span>
</div>
</div>
<div className="resend-label"></div>
</div>
<div className="transaction-item refund">
<div className="transaction-status">
<div className="status-dot gray"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">*(010333*****)</div>
<div className="transaction-details">
<span>nictest01m</span>
<span className="separator"></span>
<span> +</span>
</div>
</div>
<div className="resend-label"></div>
</div>
<div className="transaction-item approved">
<div className="transaction-status">
<div className="status-dot blue"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">*(010333*****)</div>
<div className="transaction-details">
<span>nictest01m</span>
<span className="separator"></span>
<span> +</span>
</div>
</div>
<div className="resend-label"></div>
</div>
<div className="transaction-item refund">
<div className="transaction-status">
<div className="status-dot gray"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">*(010333*****)</div>
<div className="transaction-details">
<span>nictest01m</span>
<span className="separator"></span>
<span> </span>
</div>
</div>
<div className="resend-label"></div>
</div>
<div className="transaction-item approved">
<div className="transaction-status">
<div className="status-dot blue"></div>
</div>
<div className="transaction-content">
<div className="transaction-title">*(010333*****)</div>
<div className="transaction-details">
<span>nictest01m</span>
<span className="separator">|</span>
<span> </span>
</div>
</div>
<div className="resend-label"></div>
</div>
</div>
</section>
</div>
</div>
</main>
<SmsPaymentDetailResend
bottomSmsPaymentDetailResendOn={ bottomSmsPaymentDetailResendOn }
setBottomSmsPaymentDetailResendOn={ setBottomSmsPaymentDetailResendOn }
></SmsPaymentDetailResend>
</>
);
};

View File

@@ -0,0 +1,14 @@
import { Route } from 'react-router-dom';
import { SentryRoutes } from '@/shared/configs/sentry';
import { ROUTE_NAMES } from '@/shared/constants/route-names';
import { ListPage } from './list/list-page';
export const AlarmPages = () => {
return (
<>
<SentryRoutes>
<Route path={ROUTE_NAMES.alarm.list} element={<ListPage />} />
</SentryRoutes>
</>
);
};

View File

@@ -0,0 +1,28 @@
import { AlarmList } from '@/entities/alarm/ui/alarm-list';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const ListPage = () => {
useSetHeaderTitle('알림함');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false);
return (
<>
<main className="pop">
<div className="sub-wrap">
<div className="notice-tabs">
<button className="tab36 on"></button>
<button className="tab36">/</button>
<button className="tab36"></button>
</div>
<AlarmList></AlarmList>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,16 @@
import { Route } from 'react-router-dom';
import { SentryRoutes } from '@/shared/configs/sentry';
import { ROUTE_NAMES } from '@/shared/constants/route-names';
import { InfoPage } from './info/info-page';
import { RegistrationStatusPage } from './registration-status/registration-status-page';
export const BusinessMemberPages = () => {
return (
<>
<SentryRoutes>
<Route path={ROUTE_NAMES.businessMember.info} element={<InfoPage />} />
<Route path={ROUTE_NAMES.businessMember.registrationStatus} element={<RegistrationStatusPage />} />
</SentryRoutes>
</>
);
};

View File

@@ -0,0 +1,39 @@
import { useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { BusinessMemberTab } from '@/entities/business-member/ui/business-member-tab';
import { InfoWrap } from '@/entities/business-member/ui/info-wrap';
import { BusinessMemberTabKeys } from '@/entities/business-member/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const InfoPage = () => {
const { navigate } = useNavigate();
const [activeTab, setActiveTab] = useState<BusinessMemberTabKeys>(BusinessMemberTabKeys.Info);
useSetHeaderTitle('가맹점 관리');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
useSetOnBack(() => {
navigate(PATHS.home);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<BusinessMemberTab activeTab={ activeTab }></BusinessMemberTab>
<InfoWrap></InfoWrap>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,39 @@
import { useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { BusinessMemberTab } from '@/entities/business-member/ui/business-member-tab';
import { RegistrationStatusWrap } from '@/entities/business-member/ui/registration-status-wrap';
import { BusinessMemberTabKeys } from '@/entities/business-member/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const RegistrationStatusPage = () => {
const { navigate } = useNavigate();
const [activeTab, setActiveTab] = useState<BusinessMemberTabKeys>(BusinessMemberTabKeys.RegistrationStatus);
useSetHeaderTitle('가맹점 관리');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
useSetOnBack(() => {
navigate(PATHS.home);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<BusinessMemberTab activeTab={ activeTab }></BusinessMemberTab>
<RegistrationStatusWrap></RegistrationStatusWrap>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,109 @@
import { useCallback, useEffect, useState } from 'react';
import { getLocalStorage } from '@/shared/lib';
import { StorageKeys } from '@/shared/constants/local-storage';
import { useUserInfo } from '@/entities/user/lib/use-user-info';
import { FavoriteWrapper } from '@/entities/home/ui/favorite-wrapper';
import { DayStatusBox } from '@/entities/home/ui/day-status-box';
import { HomeBottomBanner } from '@/entities/home/ui/home-bottom-banner';
import { AuthRegister } from '@/entities/home/ui/auth-reguster';
import { FooterItemActiveKey } from '@/entities/common/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetFooterCurrentPage
} from '@/widgets/sub-layout/use-sub-layout';
import moment from 'moment';
export const HomePage = () => {
useSetHeaderTitle('');
useSetHeaderType(HeaderType.Home);
useSetFooterMode(true);
useSetFooterCurrentPage(FooterItemActiveKey.Home);
const { callLogin } = useUserInfo();
const today = moment().format('YYYYMMDD').toString();
let bannerToday = getLocalStorage(StorageKeys.BottomBannerClose);
const [bottomBannerOn, setBottomBannerOn] = useState<boolean>(false);
const [authRegisterOn, setAuthRegisterOn] = useState<boolean>(false);
/*
const userParmas = {
id: 'thenaun12',
password: 'answjddl1!'
};
*/
const userParmas = {
id: 'nictest00',
password: 'nictest00'
};
const handleLogin = useCallback(async () =>{
await callLogin(userParmas);
}, []);
const checkBottomBannerOpen = () => {
if(!!bannerToday){
bannerToday = bannerToday.toString();
}
let sw = (today !== bannerToday);
setBottomBannerOn(sw);
};
const checkAuthRegisterOpen = () => {
setAuthRegisterOn(true);
};
useEffect(() => {
handleLogin();
checkBottomBannerOpen();
checkAuthRegisterOpen();
}, []);
const setBottomBannerEffect = (mode: boolean) => {
setBottomBannerOn(mode);
if(mode === false){
if(authRegisterOn){
setAuthRegisterOn(false);
setTimeout(() => {
setAuthRegisterOn(true);
}, 500)
}
}
};
/*useLoginMutation
const [userInfo] = useStore(
useShallow((state) => [state.userSlice.userInfo]),
);
*/
// useRefreshUserInfo();
return (
<>
<main>
{/*<!-- 탭 컨텐츠 영역 -->*/}
<div className="tab-content blue">
<div className="tab-pane dashboard active" id="tab1">
<FavoriteWrapper></FavoriteWrapper>
<DayStatusBox></DayStatusBox>
</div>
</div>
</main>
<HomeBottomBanner
setBottomBannerOn={ setBottomBannerEffect }
bottomBannerOn={ bottomBannerOn }
></HomeBottomBanner>
{ (!bottomBannerOn) &&
<AuthRegister
setAuthRegisterOn={ setAuthRegisterOn }
authRegisterOn={ authRegisterOn }
></AuthRegister>
}
</>
);
};

3
src/pages/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export { default as Home } from './Home';
export { default as Contact } from './Contact';
export { default as Test } from './Test';

View File

@@ -0,0 +1,39 @@
import { useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { PaymentTab } from '@/entities/payment/ui/payment-tab';
import { DataNotificationWrap } from '@/entities/payment/ui/data-notification-wrap';
import { PaymentTabKeys } from '@/entities/payment/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const DataNotificationPage = () => {
const { navigate } = useNavigate();
const [activeTab, setActiveTab] = useState<PaymentTabKeys>(PaymentTabKeys.DataNotification);
useSetHeaderTitle('결제 관리');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
useSetOnBack(() => {
navigate(PATHS.home);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<PaymentTab activeTab={ activeTab }></PaymentTab>
<DataNotificationWrap></DataNotificationWrap>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,39 @@
import { useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { PaymentTab } from '@/entities/payment/ui/payment-tab';
import { InfoWrap } from '@/entities/payment/ui/info-wrap';
import { PaymentTabKeys } from '@/entities/payment/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const InfoPage = () => {
const { navigate } = useNavigate();
const [activeTab, setActiveTab] = useState<PaymentTabKeys>(PaymentTabKeys.Info);
useSetHeaderTitle('결제 관리');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
useSetOnBack(() => {
navigate(PATHS.home);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<PaymentTab activeTab={ activeTab }></PaymentTab>
<InfoWrap></InfoWrap>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,16 @@
import { Route } from 'react-router-dom';
import { SentryRoutes } from '@/shared/configs/sentry';
import { ROUTE_NAMES } from '@/shared/constants/route-names';
import { InfoPage } from './info/info-page';
import { DataNotificationPage } from './data-notification/data-notification-page';
export const PaymentPages = () => {
return (
<>
<SentryRoutes>
<Route path={ROUTE_NAMES.payment.info} element={<InfoPage />} />
<Route path={ROUTE_NAMES.payment.dataNotification} element={<DataNotificationPage />} />
</SentryRoutes>
</>
);
};

View File

@@ -0,0 +1,108 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const SettingPage = () => {
useSetHeaderTitle('설정');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false);
return (
<>
<main className="pop">
<div className="sub-wrap">
<div className="settings-header">
<div className="settings-title"> </div>
<label className="settings-switch">
<input type="checkbox" />
<span className="slider"></span>
</label>
</div>
<div className="settings-divider"></div>
<div className="settings-section">
<div className="settings-section-title"> </div>
<div className="settings-row">
<div className="settings-row-title"> </div>
<label className="settings-switch">
<input type="checkbox" />
<span className="slider"></span>
</label>
</div>
<div className="settings-row">
<div className="settings-row-title"> </div>
<label className="settings-switch">
<input type="checkbox" />
<span className="slider"></span>
</label>
</div>
<div className="settings-row">
<div className="settings-row-title"> </div>
<label className="settings-switch">
<input type="checkbox" />
<span className="slider"></span>
</label>
</div>
<div className="settings-row">
<div className="settings-row-title">NICE소식 </div>
<label className="settings-switch">
<input type="checkbox" />
<span className="slider"></span>
</label>
</div>
</div>
<div className="settings-section nopadding">
<div className="settings-section-title"> </div>
<div className="settings-row">
<div className="settings-row-title"> </div>
<label className="settings-switch">
<input type="checkbox" />
<span className="slider"></span>
</label>
</div>
<div className="settings-row nopadding">
<div className="settings-row-title"> </div>
<label className="settings-switch">
<input type="checkbox" />
<span className="slider"></span>
</label>
</div>
</div>
<div className="settings-divider"></div>
<div className="settings-section nopadding">
<div className="settings-row link">
<div className="settings-row-title bd-style"> </div>
<label className="settings-switch">
<input type="checkbox" />
<span className="slider"></span>
</label>
</div>
</div>
<div className="settings-row link">
<div className="settings-row-title bd-style"> </div>
<div className="click">ID/PW</div>
</div>
<div className="settings-row link nopadding">
<div className="settings-row-title bd-style"> </div>
<div className="click"> </div>
</div>
<div className="settings-divider"></div>
<div className="settings-row danger">
<div className="settings-row-title bd-style"></div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,39 @@
import { useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { SettlementTab } from '@/entities/settlement/ui/settlement-tab';
import { CalendarWrap } from '@/entities/settlement/ui/calandar-wrap';
import { SettlementTabKeys } from '@/entities/settlement/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const CalendarPage = () => {
const { navigate } = useNavigate();
const [activeTab, setActiveTab] = useState<SettlementTabKeys>(SettlementTabKeys.Calendar);
useSetHeaderTitle('정산조회');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(false);
useSetOnBack(() => {
navigate(PATHS.home);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<SettlementTab activeTab={ activeTab }></SettlementTab>
<CalendarWrap></CalendarWrap>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,85 @@
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { DetailAmountInfo } from '@/entities/settlement/ui/detail-amount-info';
import { DetailSettlementInfo } from '@/entities/settlement/ui/detail-settlement-info';
import { HeaderType } from '@/entities/common/model/types';
import {
SettlementDetailParams,
DetailResponse,
DetailAmountInfoProps,
DetailSettlementInfoProps,
} from '@/entities/settlement/model/types';
import {
useSetOnBack,
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const DetailPage = () => {
const { navigate } = useNavigate();
const { tid } = useParams();
const [amountInfo, setAmountInfo] = useState<DetailAmountInfoProps>();
const [settlementInfo, setSettlementInfo] = useState<DetailSettlementInfoProps>();
const [showAmount, setShowAmount] = useState<boolean>(false);
const [showSettlement, setShowSettlement] = useState<boolean>(false);
useSetHeaderTitle('정산내역 상세');
useSetHeaderType(HeaderType.RightClose);
useSetOnBack(() => {
navigate(PATHS.settlement.list);
});
useSetFooterMode(false);
const onClickToShowInfo = (info: string) => {
if(info === 'amount'){
setShowAmount(!showAmount);
}
else if(info === 'settlement'){
setShowSettlement(!showSettlement);
}
};
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<div className="option-list">
<div className="txn-detail">
<div className="txn-num-group">
<div className="txn-amount">
<div className="value">63,736,320<span className="unit"></span></div>
{/*
<button className="chip-btn" type="button">금액상세 <img src="../images/select_arrow.svg" alt="펼치기"/></button>
*/}
</div>
<div className="txn-date">2025.06.08</div>
</div>
<div className="txn-divider minus"></div>
<DetailAmountInfo
amountInfo={ amountInfo }
show={ showAmount }
tid={ tid }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailAmountInfo>
<div className="txn-divider"></div>
<DetailSettlementInfo
settlementInfo={ settlementInfo }
show={ showSettlement }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailSettlementInfo>
</div>
</div>
<div className="apply-row">
<button className="btn-50 btn-blue flex-1"> </button>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,42 @@
import { useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { SettlementTab } from '@/entities/settlement/ui/settlement-tab';
import { ListWrap } from '@/entities/settlement/ui/list-wrap';
import { SettlementTabKeys } from '@/entities/settlement/model/types';
import { FooterItemActiveKey } from '@/entities/common/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetFooterCurrentPage,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const ListPage = () => {
const { navigate } = useNavigate();
const [activeTab, setActiveTab] = useState<SettlementTabKeys>(SettlementTabKeys.List);
useSetHeaderTitle('정산조회');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
useSetFooterCurrentPage(FooterItemActiveKey.Settlement);
useSetOnBack(() => {
navigate(PATHS.home);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<SettlementTab activeTab={ activeTab }></SettlementTab>
<ListWrap></ListWrap>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,16 @@
import { Route } from 'react-router-dom';
import { SentryRoutes } from '@/shared/configs/sentry';
import { ROUTE_NAMES } from '@/shared/constants/route-names';
import { CalendarPage } from './calendar/calendar-page';
import { ListPage } from './list/list-page';
export const SettlementPages = () => {
return (
<>
<SentryRoutes>
<Route path={ROUTE_NAMES.settlement.calendar} element={<CalendarPage />} />
<Route path={ROUTE_NAMES.settlement.list} element={<ListPage />} />
</SentryRoutes>
</>
);
};

View File

@@ -0,0 +1,18 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const FaqDetailPage = () => {
useSetHeaderTitle('자주 묻는 질문');
useSetHeaderType(HeaderType.RightClose);
useSetFooterMode(false);
return (
<>
</>
);
};

View File

@@ -0,0 +1,18 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const FaqListPage = () => {
useSetHeaderTitle('자주 묻는 질문');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
return (
<>
</>
);
};

View File

@@ -0,0 +1,48 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const NoticeDetailPage = () => {
useSetHeaderTitle('공지사항');
useSetHeaderType(HeaderType.RightClose);
useSetFooterMode(false);
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<div className="option-list pb-120">
<div className="notice-detail">
<div className="notice-detail__title">[ ] 5 (4 ) ()</div>
<div className="notice-detail__meta">2025.08.19 | </div>
<div className="notice-detail__divider"></div>
<div className="notice-detail__body">. .
25 5 (4 ) .
5 .
, .
----------- -----------
기존 : 매월 15( )/*5 출금일 : 19일()
변경 : 5월 19()
발행일 : 5월 19(*19 )
-----------------------------
</div>
</div>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,18 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const NoticeListPage = () => {
useSetHeaderTitle('공지사항');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
return (
<>
</>
);
};

View File

@@ -0,0 +1,18 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const QnaDetailPage = () => {
useSetHeaderTitle('1:1 문의');
useSetHeaderType(HeaderType.RightClose);
useSetFooterMode(false);
return (
<>
</>
);
};

View File

@@ -0,0 +1,18 @@
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const QnaListPage = () => {
useSetHeaderTitle('1:1 문의');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
return (
<>
</>
);
};

View File

@@ -0,0 +1,30 @@
import { Route } from 'react-router-dom';
import { SentryRoutes } from '@/shared/configs/sentry';
import { ROUTE_NAMES } from '@/shared/constants/route-names';
import { NoticeListPage } from './notice/list-page';
import { NoticeDetailPage } from './notice/detail-page';
import { FaqListPage } from './faq/list-page';
import { FaqDetailPage } from './faq/detail-page';
import { QnaListPage } from './qna/list-page';
import { QnaDetailPage } from './qna/detail-page';
export const SupportPages = () => {
return (
<>
<SentryRoutes>
<Route path={ROUTE_NAMES.support.notice.base}>
<Route path={ROUTE_NAMES.support.notice.list} element={<NoticeListPage />} />
<Route path={ROUTE_NAMES.support.notice.detail} element={<NoticeDetailPage />} />
</Route>
<Route path={ROUTE_NAMES.support.faq.base}>
<Route path={ROUTE_NAMES.support.faq.list} element={<FaqListPage />} />
<Route path={ROUTE_NAMES.support.faq.detail} element={<FaqDetailPage />} />
</Route>
<Route path={ROUTE_NAMES.support.qna.base}>
<Route path={ROUTE_NAMES.support.qna.list} element={<QnaListPage />} />
<Route path={ROUTE_NAMES.support.qna.detail} element={<QnaDetailPage />} />
</Route>
</SentryRoutes>
</>
);
};

View File

@@ -0,0 +1,133 @@
import { useEffect, useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useLocation } from 'react-router';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { useInvoiceDetailMutation } from '@/entities/tax/api/use-invoice-detail-mutation';
import { DetailAmountInfoSection } from '@/entities/tax/ui/detail-amount-info-section';
import { DetailPublishInfoSection } from '@/entities/tax/ui/detail-publish-info-section';
import { DetailReceiverInfoSection } from '@/entities/tax/ui/detail-receiver-info-section';
import { DetailSupplierInfoSection } from '@/entities/tax/ui/detail-supplier-info-section';
import { HeaderType } from '@/entities/common/model/types';
import {
DetailResponse,
InvoiceDetailParams,
DetailAmountInfoProps,
DetailInfoSectionKeys,
DetailPublishInfoProps,
DetailReceiverInfoProps,
DetailSupplierInfoProps
} from '@/entities/tax/model/types';
import {
useSetOnBack,
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const InvoiceDetailPage = () => {
const { navigate } = useNavigate();
const location = useLocation();
const [tid, setTid] = useState<string>(location?.state.tid);
const [amountInfo, setAmountInfo] = useState<DetailAmountInfoProps>();
const [publishInfo, setPublishInfo] = useState<DetailPublishInfoProps>();
const [receiverInfo, setReceiverInfo] = useState<DetailReceiverInfoProps>();
const [supplierInfo, setSupplierInfo] = useState<DetailSupplierInfoProps>();
const [showAmount, setShowAmount] = useState<boolean>(false);
const [showPublish, setShowPublish] = useState<boolean>(false);
const [showReceiver, setShowReceiver] = useState<boolean>(false);
const [showSupplier, setShowSupplier] = useState<boolean>(false);
useSetHeaderTitle('세금계산서 상세');
useSetHeaderType(HeaderType.RightClose);
useSetOnBack(() => {
navigate(PATHS.tax.invoice.list);
});
useSetFooterMode(false);
const { mutateAsync: invoiceDetail } = useInvoiceDetailMutation();
const onClickToShowInfo = (info: DetailInfoSectionKeys) => {
if(info === DetailInfoSectionKeys.Amount){
setShowAmount(!showAmount);
}
else if(info === DetailInfoSectionKeys.Publish){
setShowPublish(!showReceiver);
}
else if(info === DetailInfoSectionKeys.Receiver){
setShowReceiver(!showReceiver);
}
else if(info === DetailInfoSectionKeys.Supplier){
setShowSupplier(!showSupplier);
}
};
const callDetail = () => {
let invoiceDetailParams: InvoiceDetailParams = {
svcCd: 'st',
tid: tid
};
invoiceDetail(invoiceDetailParams).then((rs: DetailResponse) => {
setAmountInfo(rs.amountInfo);
setPublishInfo(rs.publishInfo);
setReceiverInfo(rs.receiverInfo);
setSupplierInfo(rs.supplierInfo);
});
};
useEffect(() => {
// callDetail();
}, []);
return (
<>
<main className="full-height">
<div className="tab-content">
<div className="tab-pane sub active">
<div className="option-list">
<div className="txn-detail">
<DetailAmountInfoSection
amountInfo={ amountInfo }
show={ showAmount }
tid={ tid }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailAmountInfoSection>
<div className="txn-divider minus"></div>
<DetailPublishInfoSection
publishInfo={ publishInfo }
show={ showAmount }
tid={ tid }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailPublishInfoSection>
<div className="txn-divider minus"></div>
<DetailReceiverInfoSection
receiverInfo={ receiverInfo }
show={ showAmount }
tid={ tid }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailReceiverInfoSection>
<div className="txn-divider"></div>
<DetailSupplierInfoSection
supplierInfo={ supplierInfo }
show={ showAmount }
tid={ tid }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailSupplierInfoSection>
</div>
</div>
</div>
</div>
</main>
</>
)
};

View File

@@ -0,0 +1,38 @@
import { useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { TaxTab } from '@/entities/tax/ui/tax-tab';
import { InvoiceListWrap } from '@/entities/tax/ui/invoice-list-wrap';
import { TaxTabKeys } from '@/entities/tax/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const InvoiceListPage = () => {
const { navigate } = useNavigate();
const [activeTab, setActiveTab] = useState<TaxTabKeys>(TaxTabKeys.InvoiceList);
useSetHeaderTitle('부가세 신고 자료');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
useSetOnBack(() => {
navigate(PATHS.home);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<TaxTab activeTab={ activeTab }></TaxTab>
<InvoiceListWrap></InvoiceListWrap>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,20 @@
import { Route } from 'react-router-dom';
import { SentryRoutes } from '@/shared/configs/sentry';
import { ROUTE_NAMES } from '@/shared/constants/route-names';
import { InvoiceListPage } from './invoice/list-page';
import { InvoiceDetailPage } from './invoice/detail-page';
import { VatReferencePage } from './vat-reference/vat-reference-page';
export const TaxPages = () => {
return (
<>
<SentryRoutes>
<Route path={ROUTE_NAMES.tax.invoice.base}>
<Route path={ROUTE_NAMES.tax.invoice.list} element={<InvoiceListPage />} />
<Route path={ROUTE_NAMES.tax.invoice.detail} element={<InvoiceDetailPage />} />
</Route>
<Route path={ROUTE_NAMES.tax.vatReference} element={<VatReferencePage />} />
</SentryRoutes>
</>
);
};

View File

@@ -0,0 +1,39 @@
import { useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { TaxTab } from '@/entities/tax/ui/tax-tab';
import { VatReferenceWrap } from '@/entities/tax/ui/vat-reference-wrap';
import { TaxTabKeys } from '@/entities/tax/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetOnBack
} from '@/widgets/sub-layout/use-sub-layout';
export const VatReferencePage = () => {
const { navigate } = useNavigate();
const [activeTab, setActiveTab] = useState<TaxTabKeys>(TaxTabKeys.VatReference);
useSetHeaderTitle('부가세 신고 자료');
useSetHeaderType(HeaderType.LeftArrow);
useSetFooterMode(true);
useSetOnBack(() => {
navigate(PATHS.home);
});
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<TaxTab activeTab={ activeTab }></TaxTab>
<VatReferenceWrap></VatReferenceWrap>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,103 @@
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router';
import { AllTransactionAllCancel } from '@/entities/transaction/ui/all-transaction-all-cancel'
import { AllTransactionPartCancel } from '@/entities/transaction/ui/all-transaction-part-cancel'
import { useAllTransactioCancleMutation } from '@/entities/transaction/api/use-all-transaction-cancel-mutation';
import { HeaderType } from '@/entities/common/model/types';
import {
AllTransactionCancelParams,
AllTransactionCancelResponse,
CancelTabKeys
} from '@/entities/transaction/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const AllTransactionCancelPage = () => {
const location = useLocation();
useSetHeaderTitle('거래 취소');
useSetHeaderType(HeaderType.RightClose);
useSetFooterMode(false);
// all or part
const [tabAction, setTabAction] = useState<CancelTabKeys>(CancelTabKeys.All);
const { mutateAsync: transactionCancel } = useAllTransactioCancleMutation();
const callTransactionCancel = () => {
let transactionCancelParams: AllTransactionCancelParams = {
tid: location?.state.tid,
cancelAmount: 0,
cancelPassword: "string",
bankCode: "string",
accountNo: "string",
accountHolder: "string",
supplyAmount: 0,
goodsVatAmount: 0,
taxFreeAmount: 0,
serviceAmount: 0
};
transactionCancel(transactionCancelParams).then((rs: AllTransactionCancelResponse) => {
console.log(rs);
});
};
useEffect(() => {
}, []);
const onClickToChangeTab = (tab: CancelTabKeys) => {
setTabAction(tab);
};
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<div className="option-list-nopadding">
<div className="subTab">
<button
className={ `subtab-btn ${(tabAction === CancelTabKeys.All)? 'active': ''}` }
onClick={ () => onClickToChangeTab(CancelTabKeys.All) }
> </button>
<button
className={ `subtab-btn ${(tabAction === CancelTabKeys.Part)? 'active': ''}` }
onClick={ () => onClickToChangeTab(CancelTabKeys.Part) }
> </button>
</div>
<div className="cancel-list">
<div className="amount-info">
<ul className="amount-list">
<li className="amount-item">
<span className="label">·&nbsp;&nbsp; </span>
<span className="value">500,000,000</span>
</li>
<li className="amount-item">
<span className="label">·&nbsp;&nbsp; </span>
<span className="value">500,000,000</span>
</li>
</ul>
</div>
{ (tabAction === CancelTabKeys.All) &&
<AllTransactionAllCancel></AllTransactionAllCancel>
}
{ (tabAction === CancelTabKeys.Part) &&
<AllTransactionPartCancel></AllTransactionPartCancel>
}
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={ () => callTransactionCancel() }
></button>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,187 @@
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { PATHS } from '@/shared/constants/paths';
import { Dialog } from '@/shared/ui/dialogs/dialog';
import { overlay } from 'overlay-kit';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { useAllTransactionDetailMutation } from '@/entities/transaction/api/use-all-transaction-detail-mutation';
import { DetailAmountInfo } from '@/entities/transaction/ui/detail-amount-info';
import { DetailImportantInfo } from '@/entities/transaction/ui/detail-important-info';
import { DetailPaymentInfo } from '@/entities/transaction/ui/detail-payment-info';
import { DetailTransactionInfo } from '@/entities/transaction/ui/detail-transaction-info';
import { DetailSettlementInfo } from '@/entities/transaction/ui/detail-settlement-info';
import { DetailPartCancelInfo } from '@/entities/transaction/ui/detail-part-cancel-info';
import { HeaderType } from '@/entities/common/model/types';
import {
PageType,
AllTransactionDetailParams,
DetailResponse,
DetailAmountInfoProps,
DetailImportantInfoProps,
DetailPaymentInfoProps,
DetailTransactionInfoProps,
DetailSettlementInfoProps,
DetailPartCancelInfoProps,
DetailInfoKeys
} from '@/entities/transaction/model/types';
import {
useSetOnBack,
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const AllTransactionDetailPage = () => {
const { navigate } = useNavigate();
const { tid } = useParams();
const [amountInfo, setAmountInfo] = useState<DetailAmountInfoProps>();
const [importantInfo, setImportantInfo] = useState<DetailImportantInfoProps>();
const [paymentInfo, setPaymentInfo] = useState<DetailPaymentInfoProps>();
const [transactionInfo, setTransactionInfo] = useState<DetailTransactionInfoProps>();
const [settlementInfo, setSettlementInfo] = useState<DetailSettlementInfoProps>();
const [partCancelInfo, setPartCancelInfo] = useState<DetailPartCancelInfoProps>();
const [showAmount, setShowAmount] = useState<boolean>(false);
const [showPayment, setShowPayment] = useState<boolean>(false);
const [showTransaction, setShowTransaction] = useState<boolean>(false);
const [showSettlement, setShowSettlement] = useState<boolean>(false);
const [showPartCancel, setShowPartCancel] = useState<boolean>(false);
useSetHeaderTitle('거래내역 상세');
useSetHeaderType(HeaderType.RightClose);
useSetOnBack(() => {
navigate(PATHS.transaction.allTransaction.list);
});
useSetFooterMode(false);
const { mutateAsync: allTransactionDetail } = useAllTransactionDetailMutation();
const callDetail = () => {
let allTransactionDetailParams: AllTransactionDetailParams = {
svcCd: 'st',
tid: tid
};
allTransactionDetail(allTransactionDetailParams).then((rs: DetailResponse) => {
setAmountInfo(rs.amountInfo);
setImportantInfo(rs.importantInfo);
setPaymentInfo(rs.paymentInfo);
setTransactionInfo(rs.transactionInfo);
setSettlementInfo(rs.settlementInfo);
setPartCancelInfo(rs.partCancelInfo);
});
};
useEffect(() => {
callDetail();
}, []);
const onClickToNavigate = (path: string) => {
let timeout = setTimeout(() => {
clearTimeout(timeout);
navigate(PATHS.transaction.allTransaction.cancel, {
state: {
tid: tid
}
});
}, 10)
};
const onClickToCancel = () => {
let msg = '거래를 취소하시겠습니까?';
overlay.open(({
isOpen,
close,
unmount
}) => {
return (
<Dialog
afterLeave={ unmount }
open={ isOpen }
onClose={ close }
onConfirmClick={ () => onClickToNavigate(PATHS.transaction.allTransaction.cancel) }
message={ msg }
buttonLabel={['취소', '확인']}
/>
);
});
};
const onClickToShowInfo = (info: DetailInfoKeys) => {
if(info === DetailInfoKeys.Amount){
setShowAmount(!showAmount);
}
else if(info === DetailInfoKeys.Payment){
setShowPayment(!showPayment);
}
else if(info === DetailInfoKeys.Transaction){
setShowTransaction(!showTransaction);
}
else if(info === DetailInfoKeys.Settlement){
setShowSettlement(!showSettlement);
}
else if(info === DetailInfoKeys.PartCancel){
setShowPartCancel(!showPartCancel);
}
};
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<div className="option-list">
<div className="txn-detail">
<DetailAmountInfo
pageType={ PageType.AllTransaction }
amountInfo={ amountInfo }
show={ showAmount }
tid={ tid }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailAmountInfo>
<div className="txn-divider minus"></div>
<DetailImportantInfo
pageType={ PageType.AllTransaction }
importantInfo={ importantInfo }
></DetailImportantInfo>
<div className="txn-divider minus"></div>
<DetailPaymentInfo
pageType={ PageType.AllTransaction }
paymentInfo={ paymentInfo }
show={ showPayment }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailPaymentInfo>
<div className="txn-divider"></div>
<DetailTransactionInfo
pageType={ PageType.AllTransaction }
transactionInfo={ transactionInfo }
show={ showTransaction }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailTransactionInfo>
<div className="txn-divider"></div>
<DetailSettlementInfo
pageType={ PageType.AllTransaction }
settlementInfo={ settlementInfo }
show={ showSettlement }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailSettlementInfo>
<div className="txn-divider"></div>
<DetailPartCancelInfo
pageType={ PageType.AllTransaction }
partCancelInfo={ partCancelInfo }
show={ showPartCancel }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailPartCancelInfo>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={ () => onClickToCancel() }
> </button>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,232 @@
import moment from 'moment';
import { ChangeEvent, useEffect, useState } from 'react';
import { NumericFormat } from 'react-number-format';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { AllTransactionList } from '@/entities/transaction/ui/all-transaction-list';
import { ListItem, PageType, SortByKeys } from '@/entities/transaction/model/types';
import { useAllTransactionListMutation } from '@/entities/transaction/api/use-all-transaction-list-mutation';
import { useAllTransactionListSummaryMutation } from '@/entities/transaction/api/use-all-transaction-list-summary-mutation';
import { useDownloadExcelMutation } from '@/entities/transaction/api/use-download-excel-mutation';
import { DEFAULT_PAGE_PARAM } from '@/entities/common/model/constants';
import { Filter } from '@/entities/transaction/ui/filter';
import { SortOptionsBox } from '@/entities/transaction/ui/sort-options-box';
import { FooterItemActiveKey } from '@/entities/common/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetOnBack,
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetFooterCurrentPage
} from '@/widgets/sub-layout/use-sub-layout';
export const AllTransactionListPage = () => {
const { navigate } = useNavigate();
const [serviceCodeOptions, setServiceCodeOptions] = useState<Array<Record<string, any>>>();
const [selectedServiceCode, setSelectedServiceCode] = useState<string>('st');
const [sortBy, setSortBy] = useState<SortByKeys>(SortByKeys.New);
const [listItems, setListItems] = useState({});
const [totalTransactionCount, setTotalTransactionCount] = useState<number>(0);
const [totalTransactionAmount, setTotalTransactionAmount] = useState<number>(0);
const [filterOn, setFilterOn] = useState<boolean>(false);
const [pageParam, setPageParam] = useState(DEFAULT_PAGE_PARAM);
const [fromDate, setFromDate] = useState(moment().subtract(1, 'month').format('YYYYMMDD'));
const [toDate, setToDate] = useState(moment().format('YYYYMMDD'));
useSetHeaderTitle('거래내역 조회');
useSetHeaderType(HeaderType.LeftArrow);
useSetOnBack(() => {
navigate(PATHS.home);
});
useSetFooterMode(true);
useSetFooterCurrentPage(FooterItemActiveKey.Transaction);
const { mutateAsync: allTransactionList } = useAllTransactionListMutation();
const { mutateAsync: allTransactionListSummary } = useAllTransactionListSummaryMutation();
const { mutateAsync: downloadExcel } = useDownloadExcelMutation();
const callList = (option?: {
sortBy?: string,
val?: string
}) => {
let listSummaryParams = {
moid: 'string',
tid: 'string',
fromDate: fromDate,
toDate: toDate,
stateCode: '0',
serviceCode: (option?.val)? option.val: selectedServiceCode,
minAmount: 0,
maxAmount: 0,
dateCl: 'TRANS',
goodsName: 'string',
cardCode: 'st',
bankCode: 'str',
searchCl: 'CARD_NO',
searchValue: 'string',
};
pageParam.sortBy = (option?.sortBy)? option.sortBy: sortBy;
setPageParam(pageParam);
let listParams = {
...listSummaryParams,
...{page: pageParam}
};
allTransactionList(listParams).then((rs) => {
setListItems(assembleData(rs.content));
});
allTransactionListSummary(listSummaryParams).then((rs) => {
setTotalTransactionAmount(rs.totalTransactionAmount);
setTotalTransactionCount(rs.totalTransactionCount);
});
};
const assembleData = (content: Array<ListItem>) => {
let data: any = {};
if(content && content.length > 0){
for(let i=0;i<content?.length;i++){
let stateDate = content[i]?.stateDate;
let groupDate = stateDate?.substring(0, 8);
if(!!groupDate && !data.hasOwnProperty(groupDate)){
data[groupDate] = [];
}
if(!!groupDate && data.hasOwnProperty(groupDate)){
data[groupDate].push(content[i]);
}
}
}
return data;
};
const onClickToOpenFilter = () => {
setFilterOn(!filterOn);
};
const onClickToDownloadExcel = () => {
// tid??? 확인 필요
downloadExcel({
// tid: tid
}).then((rs) => {
});
};
const onCliCkToSort = (sort: SortByKeys) => {
setSortBy(sort);
callList({sortBy: sort});
};
const callServiceCodeOptions = () => {
let options = [
{text: '계좌간편결제', value: 'simple'},
{text: '신용카드', value: 'card'},
{text: '계좌이체', value: 'transfer'},
{text: '휴대폰', value: 'phone'},
{text: '문화상품권', value: 'gift'},
{text: '티머니페이', value: '티머니페이'},
];
setServiceCodeOptions(options);
setSelectedServiceCode('simple');
};
const onChangeSelectedServiceCode = (e: ChangeEvent<HTMLSelectElement>) => {
let val = e.target.value;
// onchagne 에서 useState가 즉시 반영 안되므로 값을 직접 바로 넘긴다.
setSelectedServiceCode(val);
callList({val: val});
};
const getServiceCodeOptions = () => {
let rs = [];
if(!!serviceCodeOptions && serviceCodeOptions.length > 0)
for(let i=0;i<serviceCodeOptions.length;i++){
rs.push(
<option value={serviceCodeOptions[i]?.value}>{ serviceCodeOptions[i]?.text }</option>
)
}
return rs;
};
useEffect(() => {
callServiceCodeOptions();
callList();
}, []);
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<div className="summary-section">
<div className="summary-label"></div>
<div className="summary-amount">
<span className="amount-text">
<NumericFormat
value={ totalTransactionAmount }
thousandSeparator
displayType="text"
suffix={ '원' }
></NumericFormat>
</span>
<div className="summary-actions">
<button className="filter-btn">
<img
src={ IMAGE_ROOT + '/ico_setting.svg' }
alt="검색옵션"
onClick={ () => onClickToOpenFilter() }
/>
</button>
<button className="download-btn">
<img
src={ IMAGE_ROOT + '/ico_download.svg' }
alt="다운로드"
onClick={ () => onClickToDownloadExcel() }
/>
</button>
</div>
</div>
<div className="summary-details">
<div className="detail-item">
<span className="detail-label"></span>
<span className="detail-value">{ moment(fromDate).format('YYYY.MM.DD') } ~ { moment(toDate).format('YYYY.MM.DD') }</span>
</div>
<div className="detail-item">
<span className="detail-label"></span>
<span className="detail-value">
<NumericFormat
value={ totalTransactionCount }
thousandSeparator
displayType="text"
prefix={ '총 ' }
suffix={ ' 건' }
></NumericFormat>
</span>
</div>
</div>
</div>
<div className="filter-section">
<SortOptionsBox
sortBy={ sortBy }
onCliCkToSort={ onCliCkToSort }
></SortOptionsBox>
<select
value={ selectedServiceCode }
onChange={ (e) => onChangeSelectedServiceCode(e) }>
{ getServiceCodeOptions() }
</select>
</div>
<AllTransactionList
listItems={ listItems }
pageType={ PageType.AllTransaction }
></AllTransactionList>
</div>
</div>
</main>
<Filter
filterOn={ filterOn }
setFilterOn={ setFilterOn }
></Filter>
</>
);
};

View File

@@ -0,0 +1,188 @@
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { PATHS } from '@/shared/constants/paths';
import { Dialog } from '@/shared/ui/dialogs/dialog';
import { overlay } from 'overlay-kit';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { useBillingDetailMutation } from '@/entities/transaction/api/use-billing-detail-mutation';
import { DetailAmountInfo } from '@/entities/transaction/ui/detail-amount-info';
import { DetailImportantInfo } from '@/entities/transaction/ui/detail-important-info';
import { DetailPaymentInfo } from '@/entities/transaction/ui/detail-payment-info';
import { DetailTransactionInfo } from '@/entities/transaction/ui/detail-transaction-info';
import { DetailSettlementInfo } from '@/entities/transaction/ui/detail-settlement-info';
import { DetailPartCancelInfo } from '@/entities/transaction/ui/detail-part-cancel-info';
import { HeaderType } from '@/entities/common/model/types';
import {
PageType,
BillingDetailParams,
DetailResponse,
DetailAmountInfoProps,
DetailImportantInfoProps,
DetailPaymentInfoProps,
DetailTransactionInfoProps,
DetailSettlementInfoProps,
DetailPartCancelInfoProps,
DetailInfoKeys
} from '@/entities/transaction/model/types';
import {
useSetOnBack,
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const BillingDetailPage = () => {
const { navigate } = useNavigate();
const { tid } = useParams();
const [amountInfo, setAmountInfo] = useState<DetailAmountInfoProps>();
const [importantInfo, setImportantInfo] = useState<DetailImportantInfoProps>();
const [paymentInfo, setPaymentInfo] = useState<DetailPaymentInfoProps>();
const [transactionInfo, setTransactionInfo] = useState<DetailTransactionInfoProps>();
const [settlementInfo, setSettlementInfo] = useState<DetailSettlementInfoProps>();
const [partCancelInfo, setPartCancelInfo] = useState<DetailPartCancelInfoProps>();
const [showAmount, setShowAmount] = useState<boolean>(false);
const [showPayment, setShowPayment] = useState<boolean>(false);
const [showTransaction, setShowTransaction] = useState<boolean>(false);
const [showSettlement, setShowSettlement] = useState<boolean>(false);
const [showPartCancel, setShowPartCancel] = useState<boolean>(false);
useSetHeaderTitle('빌링 상세');
useSetHeaderType(HeaderType.RightClose);
useSetOnBack(() => {
navigate(PATHS.transaction.billing.list);
});
useSetFooterMode(false);
const { mutateAsync: billingDetail } = useBillingDetailMutation();
const callDetail = () => {
let billingDetailParams: BillingDetailParams = {
svcCd: 'st',
tid: tid
};
billingDetail(billingDetailParams).then((rs: DetailResponse) => {
setAmountInfo(rs.amountInfo);
setImportantInfo(rs.importantInfo);
setPaymentInfo(rs.paymentInfo);
setTransactionInfo(rs.transactionInfo);
setSettlementInfo(rs.settlementInfo);
setPartCancelInfo(rs.partCancelInfo);
});
};
useEffect(() => {
callDetail();
}, []);
const onClickToNavigate = (path: string) => {
let timeout = setTimeout(() => {
clearTimeout(timeout);
navigate(PATHS.transaction.allTransaction.cancel, {
state: {
tid: tid
}
});
}, 10)
};
const onClickToCancel = () => {
let msg = '거래를 취소하시겠습니까?';
overlay.open(({
isOpen,
close,
unmount
}) => {
return (
<Dialog
afterLeave={ unmount }
open={ isOpen }
onClose={ close }
onConfirmClick={ () => onClickToNavigate(PATHS.transaction.allTransaction.cancel) }
message={ msg }
buttonLabel={['취소', '확인']}
/>
);
});
};
const onClickToShowInfo = (info: DetailInfoKeys) => {
if(info === DetailInfoKeys.Amount){
setShowAmount(!showAmount);
}
else if(info === DetailInfoKeys.Payment){
setShowPayment(!showPayment);
}
else if(info === DetailInfoKeys.Transaction){
setShowTransaction(!showTransaction);
}
else if(info === DetailInfoKeys.Settlement){
setShowSettlement(!showSettlement);
}
else if(info === DetailInfoKeys.PartCancel){
setShowPartCancel(!showPartCancel);
}
};
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<div className="option-list">
<div className="txn-detail">
<DetailAmountInfo
pageType={ PageType.Billing }
amountInfo={ amountInfo }
show={ showAmount }
tid={ tid }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailAmountInfo>
<div className="txn-divider minus"></div>
<DetailImportantInfo
pageType={ PageType.Billing }
importantInfo={ importantInfo }
></DetailImportantInfo>
<div className="txn-divider minus"></div>
<DetailPaymentInfo
pageType={ PageType.Billing }
paymentInfo={ paymentInfo }
show={ showPayment }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailPaymentInfo>
<div className="txn-divider"></div>
<DetailTransactionInfo
pageType={ PageType.Billing }
transactionInfo={ transactionInfo }
show={ showTransaction }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailTransactionInfo>
<div className="txn-divider"></div>
<DetailSettlementInfo
pageType={ PageType.Billing }
settlementInfo={ settlementInfo }
show={ showSettlement }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailSettlementInfo>
<div className="txn-divider"></div>
<DetailPartCancelInfo
pageType={ PageType.Billing }
partCancelInfo={ partCancelInfo }
show={ showPartCancel }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailPartCancelInfo>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={ () => onClickToCancel() }
> </button>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,187 @@
import moment from 'moment';
import { useEffect, useState } from 'react';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { BillingList } from '@/entities/transaction/ui/billing-list';
import { ListItem, PageType, SortByKeys } from '@/entities/transaction/model/types';
import { useBillingListMutation } from '@/entities/transaction/api/use-billing-list-mutation';
import { useDownloadExcelMutation } from '@/entities/transaction/api/use-download-excel-mutation';
import { DEFAULT_PAGE_PARAM } from '@/entities/common/model/constants';
import { Filter } from '@/entities/transaction/ui/filter';
import { SortOptionsBox } from '@/entities/transaction/ui/sort-options-box';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetOnBack,
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
const serviceCodes = [
{name: '전체', key: 'all'},
{name: '진행중', key: 'process'},
{name: '성공', key: 'success'},
{name: '요청취소', key: 'cancel'}
];
export const BillingListPage = () => {
const { navigate } = useNavigate();
const [selectedServiceCode, setSelectedServiceCode] = useState<string>('all');
const [sortBy, setSortBy] = useState<SortByKeys>(SortByKeys.New);
const [listItems, setListItems] = useState({});
const [filterOn, setFilterOn] = useState<boolean>(false);
const [pageParam, setPageParam] = useState(DEFAULT_PAGE_PARAM);
const [fromDate, setFromDate] = useState(moment().subtract(1, 'month').format('YYYYMMDD'));
const [toDate, setToDate] = useState(moment().format('YYYYMMDD'));
useSetHeaderTitle('빌링');
useSetHeaderType(HeaderType.LeftArrow);
useSetOnBack(() => {
navigate(PATHS.home);
});
useSetFooterMode(true);
const { mutateAsync: billingList } = useBillingListMutation();
const { mutateAsync: downloadExcel } = useDownloadExcelMutation();
const callList = (option?: {
sortBy?: string,
val?: string
}) => {
let listSummaryParams = {
moid: 'string',
tid: 'string',
fromDate: fromDate,
toDate: toDate,
stateCode: '0',
serviceCode: (option?.val)? option.val: selectedServiceCode,
minAmount: 0,
maxAmount: 0,
dateCl: 'TRANS',
goodsName: 'string',
cardCode: 'st',
bankCode: 'str',
searchCl: 'CARD_NO',
searchValue: 'string',
};
pageParam.sortBy = (option?.sortBy)? option.sortBy: sortBy;
setPageParam(pageParam);
let listParam = {
...listSummaryParams,
...{page: pageParam}
};
billingList(listParam).then((rs) => {
setListItems(assembleData(rs.content));
});
}
const assembleData = (content: Array<ListItem>) => {
let data: any = {};
if(content && content.length > 0){
for(let i=0;i<content?.length;i++){
let stateDate = content[i]?.stateDate;
let groupDate = stateDate?.substring(0, 8);
if(!!groupDate && !data.hasOwnProperty(groupDate)){
data[groupDate] = [];
}
if(!!groupDate && data.hasOwnProperty(groupDate)){
data[groupDate].push(content[i]);
}
}
}
return data;
};
const onClickToOpenFilter = () => {
setFilterOn(!filterOn);
};
const onClickToDownloadExcel = () => {
// tid??? 확인 필요
downloadExcel({
// tid: tid
}).then((rs) => {
});
};
const onCliCkToSort = (sort: SortByKeys) => {
setSortBy(sort);
callList({sortBy: sort});
};
const onClickToServiceCode = (val: string) => {
setSelectedServiceCode(val);
callList({val: val});
};
useEffect(() => {
callList();
}, []);
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<div className="summary-section">
<div className="credit-controls">
<div>
<input
type="text"
className="credit-period"
value={ moment(fromDate).format('YYYY.MM.DD') + '-' + moment(toDate).format('YYYY.MM.DD') }
readOnly={ true }
/>
<button className="filter-btn">
<img
src={ IMAGE_ROOT + '/ico_setting.svg' }
alt="검색옵션"
onClick={ () => onClickToOpenFilter() }
/>
</button>
</div>
<button className="download-btn">
<img
src={ IMAGE_ROOT + '/ico_download.svg' }
alt="다운로드"
onClick={ () => onClickToDownloadExcel() }
/>
</button>
</div>
</div>
<div className="filter-section">
<SortOptionsBox
sortBy={ sortBy }
onCliCkToSort={ onCliCkToSort }
></SortOptionsBox>
<div className="excrow">
<div className="full-menu-keywords no-padding">
{
serviceCodes.map((value, index) => (
<span
key={ `key-service-code=${ index }` }
className={ `keyword-tag ${(selectedServiceCode === value.key)? 'active': ''}` }
onClick={ () => onClickToServiceCode(value.key) }
>{ value.name }</span>
))
}
</div>
</div>
</div>
<BillingList
listItems={ listItems }
pageType={ PageType.Billing }
></BillingList>
</div>
</div>
</main>
<Filter
filterOn={ filterOn }
setFilterOn={ setFilterOn }
></Filter>
</>
);
};

View File

@@ -0,0 +1,123 @@
import { PATHS } from '@/shared/constants/paths';
import { IMAGE_ROOT } from '@/shared/constants/common';
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';
export const BillingPaymentRequestPage = () => {
const { navigate } = useNavigate();
useSetHeaderTitle('빌링 결제 신청');
useSetHeaderType(HeaderType.RightClose);
useSetOnBack(() => {
navigate(PATHS.transaction.billing.list);
});
useSetFooterMode(false);
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<div className="option-list">
<div className="billing-title"> </div>
<div className="billing-form">
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value="BIKYvattest01m"
readOnly={ true }
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value="테스트상품123"
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value="1,000,000"
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value="P146733723"
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> <span>*</span></div>
<div className="billing-field">
<input
type="text"
value="김테스트"
/>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> </div>
<div className="billing-field">
<div className="input-wrapper date wid-100">
<input
type="text"
value="2025/03/21"
placeholder=""
/>
<button
className="date-btn"
type="button"
>
<img
src={ IMAGE_ROOT + '/ico_date.svg' }
alt="clear"
/>
</button>
</div>
</div>
</div>
<div className="billing-row">
<div className="billing-label"> </div>
<div className="billing-field">
<select>
<option value=""></option>
<option value="0"></option>
<option value="2">2</option>
<option value="3">3</option>
<option value="6">6</option>
</select>
</div>
</div>
</div>
</div>
<div className="apply-row">
<button className="btn-50 btn-blue flex-1"> </button>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,188 @@
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { PATHS } from '@/shared/constants/paths';
import { Dialog } from '@/shared/ui/dialogs/dialog';
import { overlay } from 'overlay-kit';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { useCashReceitDetailMutation } from '@/entities/transaction/api/use-cash-receit-detail-mutation';
import { DetailAmountInfo } from '@/entities/transaction/ui/detail-amount-info';
import { DetailImportantInfo } from '@/entities/transaction/ui/detail-important-info';
import { DetailPaymentInfo } from '@/entities/transaction/ui/detail-payment-info';
import { DetailTransactionInfo } from '@/entities/transaction/ui/detail-transaction-info';
import { DetailSettlementInfo } from '@/entities/transaction/ui/detail-settlement-info';
import { DetailPartCancelInfo } from '@/entities/transaction/ui/detail-part-cancel-info';
import { HeaderType } from '@/entities/common/model/types';
import {
PageType,
CashReceitDetailParams,
DetailResponse,
DetailAmountInfoProps,
DetailImportantInfoProps,
DetailPaymentInfoProps,
DetailTransactionInfoProps,
DetailSettlementInfoProps,
DetailPartCancelInfoProps,
DetailInfoKeys
} from '@/entities/transaction/model/types';
import {
useSetOnBack,
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const CashReceitDetailPage = () => {
const { navigate } = useNavigate();
const { tid } = useParams();
const [amountInfo, setAmountInfo] = useState<DetailAmountInfoProps>();
const [importantInfo, setImportantInfo] = useState<DetailImportantInfoProps>();
const [paymentInfo, setPaymentInfo] = useState<DetailPaymentInfoProps>();
const [transactionInfo, setTransactionInfo] = useState<DetailTransactionInfoProps>();
const [settlementInfo, setSettlementInfo] = useState<DetailSettlementInfoProps>();
const [partCancelInfo, setPartCancelInfo] = useState<DetailPartCancelInfoProps>();
const [showAmount, setShowAmount] = useState<boolean>(false);
const [showPayment, setShowPayment] = useState<boolean>(false);
const [showTransaction, setShowTransaction] = useState<boolean>(false);
const [showSettlement, setShowSettlement] = useState<boolean>(false);
const [showPartCancel, setShowPartCancel] = useState<boolean>(false);
useSetHeaderTitle('현금영수증 상세');
useSetHeaderType(HeaderType.RightClose);
useSetOnBack(() => {
navigate(PATHS.transaction.cashReceit.list);
});
useSetFooterMode(false);
const { mutateAsync: escroDetail } = useCashReceitDetailMutation();
const callDetail = () => {
let cashReceitDetailParams: CashReceitDetailParams = {
svcCd: 'st',
tid: tid
};
escroDetail(cashReceitDetailParams).then((rs: DetailResponse) => {
setAmountInfo(rs.amountInfo);
setImportantInfo(rs.importantInfo);
setPaymentInfo(rs.paymentInfo);
setTransactionInfo(rs.transactionInfo);
setSettlementInfo(rs.settlementInfo);
setPartCancelInfo(rs.partCancelInfo);
});
};
useEffect(() => {
callDetail();
}, []);
const onClickToNavigate = (path: string) => {
let timeout = setTimeout(() => {
clearTimeout(timeout);
navigate(PATHS.transaction.allTransaction.cancel, {
state: {
tid: tid
}
});
}, 10)
};
const onClickToCancel = () => {
let msg = '거래를 취소하시겠습니까?';
overlay.open(({
isOpen,
close,
unmount
}) => {
return (
<Dialog
afterLeave={ unmount }
open={ isOpen }
onClose={ close }
onConfirmClick={ () => onClickToNavigate(PATHS.transaction.allTransaction.cancel) }
message={ msg }
buttonLabel={['취소', '확인']}
/>
);
});
};
const onClickToShowInfo = (info: DetailInfoKeys) => {
if(info === DetailInfoKeys.Amount){
setShowAmount(!showAmount);
}
else if(info === DetailInfoKeys.Payment){
setShowPayment(!showPayment);
}
else if(info === DetailInfoKeys.Transaction){
setShowTransaction(!showTransaction);
}
else if(info === DetailInfoKeys.Settlement){
setShowSettlement(!showSettlement);
}
else if(info === DetailInfoKeys.PartCancel){
setShowPartCancel(!showPartCancel);
}
};
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<div className="option-list">
<div className="txn-detail">
<DetailAmountInfo
pageType={ PageType.CashReceit }
amountInfo={ amountInfo }
show={ showAmount }
tid={ tid }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailAmountInfo>
<div className="txn-divider minus"></div>
<DetailImportantInfo
pageType={ PageType.CashReceit }
importantInfo={ importantInfo }
></DetailImportantInfo>
<div className="txn-divider minus"></div>
<DetailPaymentInfo
pageType={ PageType.CashReceit }
paymentInfo={ paymentInfo }
show={ showPayment }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailPaymentInfo>
<div className="txn-divider"></div>
<DetailTransactionInfo
pageType={ PageType.CashReceit }
transactionInfo={ transactionInfo }
show={ showTransaction }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailTransactionInfo>
<div className="txn-divider"></div>
<DetailSettlementInfo
pageType={ PageType.CashReceit }
settlementInfo={ settlementInfo }
show={ showSettlement }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailSettlementInfo>
<div className="txn-divider"></div>
<DetailPartCancelInfo
pageType={ PageType.CashReceit }
partCancelInfo={ partCancelInfo }
show={ showPartCancel }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailPartCancelInfo>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={ () => onClickToCancel() }
> </button>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,69 @@
import { useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { CashReceitHandWrittenIssuanceStep1 } from '@/entities/transaction/ui/cash-receit-hand-written-issuance-step1';
import { CashReceitHandWrittenIssuanceStep2 } from '@/entities/transaction/ui/cash-receit-hand-written-issuance-step2';
import { ProcessStep } from '@/entities/transaction/model/types';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const CashReceitHandWrittenIssuancePage = () => {
const { navigate } = useNavigate();
// 1 or 2
const [processStep, setProcessStep] = useState<ProcessStep>(ProcessStep.One);
useSetHeaderTitle('수기 발행');
useSetHeaderType(HeaderType.RightClose);
useSetFooterMode(false);
const onClickToChangeTab = () => {
if(processStep === ProcessStep.One){
setProcessStep(ProcessStep.Two);
}
else if(processStep === ProcessStep.Two){
// 완료시?
alert('완료');
navigate(PATHS.transaction.cashReceit.list);
}
};
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<div className="option-list">
<div className="issue-progress">
<div className="bar">
<div
className="fill"
style={{ width: '50%' }}
></div>
</div>
{ (processStep === ProcessStep.One) &&
<CashReceitHandWrittenIssuanceStep1></CashReceitHandWrittenIssuanceStep1>
}
{ (processStep === ProcessStep.Two) &&
<CashReceitHandWrittenIssuanceStep2
setProcessStep={ setProcessStep }
></CashReceitHandWrittenIssuanceStep2>
}
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={ () => onClickToChangeTab() }
></button>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,237 @@
import moment from 'moment';
import { useEffect, useState } from 'react';
import { NumericFormat } from 'react-number-format';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { CashReceitList } from '@/entities/transaction/ui/cash-receit-list';
import { ListItem, PageType, SortByKeys } from '@/entities/transaction/model/types';
import { useCashReceitListMutation } from '@/entities/transaction/api/use-cash-receit-list-mutation';
import { useCashReceitListSummaryMutation } from '@/entities/transaction/api/use-cash-receit-list-summary-mutation';
import { useDownloadExcelMutation } from '@/entities/transaction/api/use-download-excel-mutation';
import { DEFAULT_PAGE_PARAM } from '@/entities/common/model/constants';
import { Filter } from '@/entities/transaction/ui/filter';
import { SortOptionsBox } from '@/entities/transaction/ui/sort-options-box';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetOnBack,
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
const serviceCodes = [
{name: '전체', key: 'all'},
{name: '승인', key: 'approval'},
{name: '취소', key: 'cancel'}
];
export const CashReceitListPage = () => {
const { navigate } = useNavigate();
const [selectedServiceCode, setSelectedServiceCode] = useState<string>('all');
const [sortBy, setSortBy] = useState<SortByKeys>(SortByKeys.New);
const [listItems, setListItems] = useState({});
const [totalTransactionCount, setTotalTransactionCount] = useState<number>(0);
const [totalTransactionAmount, setTotalTransactionAmount] = useState<number>(0);
const [filterOn, setFilterOn] = useState<boolean>(false);
const [pageParam, setPageParam] = useState(DEFAULT_PAGE_PARAM);
const [fromDate, setFromDate] = useState(moment().subtract(1, 'month').format('YYYYMMDD'));
const [toDate, setToDate] = useState(moment().format('YYYYMMDD'));
useSetHeaderTitle('현금영수증');
useSetHeaderType(HeaderType.LeftArrow);
useSetOnBack(() => {
navigate(PATHS.home);
});
useSetFooterMode(true);
const { mutateAsync: cashReceitList } = useCashReceitListMutation();
const { mutateAsync: cashReceitListSummary } = useCashReceitListSummaryMutation();
const { mutateAsync: downloadExcel } = useDownloadExcelMutation();
const callList = (option?: {
sortBy?: string,
val?: string
}) => {
let listSummaryParams = {
moid: 'string',
tid: 'string',
fromDate: fromDate,
toDate: toDate,
stateCode: '0',
serviceCode: (option?.val)? option.val: selectedServiceCode,
minAmount: 0,
maxAmount: 0,
dateCl: 'TRANS',
goodsName: 'string',
cardCode: 'st',
bankCode: 'str',
searchCl: 'CARD_NO',
searchValue: 'string',
};
pageParam.sortBy = (option?.sortBy)? option.sortBy: sortBy;
setPageParam(pageParam);
let listParams = {
...listSummaryParams,
...{page: pageParam}
};
cashReceitList(listParams).then((rs) => {
setListItems(assembleData(rs.content));
});
cashReceitListSummary(listSummaryParams).then((rs) => {
setTotalTransactionAmount(rs.totalTransactionAmount);
setTotalTransactionCount(rs.totalTransactionCount);
});
};
const assembleData = (content: Array<ListItem>) => {
let data: any = {};
if(content && content.length > 0){
for(let i=0;i<content?.length;i++){
let stateDate = content[i]?.stateDate;
let groupDate = stateDate?.substring(0, 8);
if(!!groupDate && !data.hasOwnProperty(groupDate)){
data[groupDate] = [];
}
if(!!groupDate && data.hasOwnProperty(groupDate)){
data[groupDate].push(content[i]);
}
}
}
return data;
};
const onClickToOpenFilter = () => {
setFilterOn(!filterOn);
};
const onClickToDownloadExcel = () => {
// tid??? 확인 필요
downloadExcel({
// tid: tid
}).then((rs) => {
});
};
const onCliCkToSort = (sort: SortByKeys) => {
setSortBy(sort);
callList({sortBy: sort});
};
const onClickToServiceCode = (val: string) => {
setSelectedServiceCode(val);
callList({val: val});
};
useEffect(() => {
callList();
}, []);
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<div className="summary-section">
<div className="credit-controls">
<div>
<input
type="text"
className="credit-period"
value={ moment(fromDate).format('YYYY.MM.DD') + '-' + moment(toDate).format('YYYY.MM.DD') }
readOnly={ true }
/>
<button className="filter-btn">
<img
src={ IMAGE_ROOT + '/ico_setting.svg' }
alt="검색옵션"
onClick={ () => onClickToOpenFilter() }
/>
</button>
</div>
<button className="download-btn">
<img
src={ IMAGE_ROOT + '/ico_download.svg' }
alt="다운로드"
onClick={ () => onClickToDownloadExcel() }
/>
</button>
</div>
<div className="credit-summary">
<div className="row">
<span className="label"></span>
<strong className="amount22">
<NumericFormat
value={ 83745200 }
thousandSeparator
displayType="text"
suffix={ '원' }
></NumericFormat>
</strong>
<span className="count">
<NumericFormat
value={ 2745 }
thousandSeparator
displayType="text"
prefix={ '(' }
suffix={ '건)' }
></NumericFormat>
</span>
</div>
<div className="row">
<span className="label"></span>
<strong className="amount19">
<NumericFormat
value={ 534407 }
thousandSeparator
displayType="text"
suffix={ '원' }
></NumericFormat>
</strong>
<span className="count">
<NumericFormat
value={ 32 }
thousandSeparator
displayType="text"
prefix={ '(' }
suffix={ '건)' }
></NumericFormat>
</span>
</div>
</div>
</div>
<div className="filter-section">
<SortOptionsBox
sortBy={ sortBy }
onCliCkToSort={ onCliCkToSort }
></SortOptionsBox>
<div>
<div className="full-menu-keywords no-padding">
{
serviceCodes.map((value, index) => (
<span
key={ `key-service-code=${ index }` }
className={ `keyword-tag ${(selectedServiceCode === value.key)? 'active': ''}` }
onClick={ () => onClickToServiceCode(value.key) }
>{ value.name }</span>
))
}
</div>
</div>
</div>
<CashReceitList
listItems={ listItems }
pageType={ PageType.CashReceit }
></CashReceitList>
</div>
</div>
</main>
<Filter
filterOn={ filterOn }
setFilterOn={ setFilterOn }
></Filter>
</>
);
};

View File

@@ -0,0 +1,188 @@
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { PATHS } from '@/shared/constants/paths';
import { Dialog } from '@/shared/ui/dialogs/dialog';
import { overlay } from 'overlay-kit';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { useEscroDetailMutation } from '@/entities/transaction/api/use-escro-detail-mutation';
import { DetailAmountInfo } from '@/entities/transaction/ui/detail-amount-info';
import { DetailImportantInfo } from '@/entities/transaction/ui/detail-important-info';
import { DetailPaymentInfo } from '@/entities/transaction/ui/detail-payment-info';
import { DetailTransactionInfo } from '@/entities/transaction/ui/detail-transaction-info';
import { DetailSettlementInfo } from '@/entities/transaction/ui/detail-settlement-info';
import { DetailPartCancelInfo } from '@/entities/transaction/ui/detail-part-cancel-info';
import { HeaderType } from '@/entities/common/model/types';
import {
PageType,
EscroDetailParams,
DetailResponse,
DetailAmountInfoProps,
DetailImportantInfoProps,
DetailPaymentInfoProps,
DetailTransactionInfoProps,
DetailSettlementInfoProps,
DetailPartCancelInfoProps,
DetailInfoKeys
} from '@/entities/transaction/model/types';
import {
useSetOnBack,
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
export const EscroDetailPage = () => {
const { navigate } = useNavigate();
const { tid } = useParams();
const [amountInfo, setAmountInfo] = useState<DetailAmountInfoProps>();
const [importantInfo, setImportantInfo] = useState<DetailImportantInfoProps>();
const [paymentInfo, setPaymentInfo] = useState<DetailPaymentInfoProps>();
const [transactionInfo, setTransactionInfo] = useState<DetailTransactionInfoProps>();
const [settlementInfo, setSettlementInfo] = useState<DetailSettlementInfoProps>();
const [partCancelInfo, setPartCancelInfo] = useState<DetailPartCancelInfoProps>();
const [showAmount, setShowAmount] = useState<boolean>(false);
const [showPayment, setShowPayment] = useState<boolean>(false);
const [showTransaction, setShowTransaction] = useState<boolean>(false);
const [showSettlement, setShowSettlement] = useState<boolean>(false);
const [showPartCancel, setShowPartCancel] = useState<boolean>(false);
useSetHeaderTitle('에스크로 상세');
useSetHeaderType(HeaderType.RightClose);
useSetOnBack(() => {
navigate(PATHS.transaction.escro.list);
});
useSetFooterMode(false);
const { mutateAsync: escroDetail } = useEscroDetailMutation();
const callDetail = () => {
let escroDetailParams: EscroDetailParams = {
svcCd: 'st',
tid: tid
};
escroDetail(escroDetailParams).then((rs: DetailResponse) => {
setAmountInfo(rs.amountInfo);
setImportantInfo(rs.importantInfo);
setPaymentInfo(rs.paymentInfo);
setTransactionInfo(rs.transactionInfo);
setSettlementInfo(rs.settlementInfo);
setPartCancelInfo(rs.partCancelInfo);
});
};
useEffect(() => {
callDetail();
}, []);
const onClickToNavigate = (path: string) => {
let timeout = setTimeout(() => {
clearTimeout(timeout);
navigate(PATHS.transaction.allTransaction.cancel, {
state: {
tid: tid
}
});
}, 10)
};
const onClickToCancel = () => {
let msg = '거래를 취소하시겠습니까?';
overlay.open(({
isOpen,
close,
unmount
}) => {
return (
<Dialog
afterLeave={ unmount }
open={ isOpen }
onClose={ close }
onConfirmClick={ () => onClickToNavigate(PATHS.transaction.allTransaction.cancel) }
message={ msg }
buttonLabel={['취소', '확인']}
/>
);
});
};
const onClickToShowInfo = (info: DetailInfoKeys) => {
if(info === DetailInfoKeys.Amount){
setShowAmount(!showAmount);
}
else if(info === DetailInfoKeys.Payment){
setShowPayment(!showPayment);
}
else if(info === DetailInfoKeys.Transaction){
setShowTransaction(!showTransaction);
}
else if(info === DetailInfoKeys.Settlement){
setShowSettlement(!showSettlement);
}
else if(info === DetailInfoKeys.PartCancel){
setShowPartCancel(!showPartCancel);
}
};
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active">
<div className="option-list">
<div className="txn-detail">
<DetailAmountInfo
pageType={ PageType.Escro }
amountInfo={ amountInfo }
show={ showAmount }
tid={ tid }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailAmountInfo>
<div className="txn-divider minus"></div>
<DetailImportantInfo
pageType={ PageType.Escro }
importantInfo={ importantInfo }
></DetailImportantInfo>
<div className="txn-divider minus"></div>
<DetailPaymentInfo
pageType={ PageType.Escro }
paymentInfo={ paymentInfo }
show={ showPayment }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailPaymentInfo>
<div className="txn-divider"></div>
<DetailTransactionInfo
pageType={ PageType.Escro }
transactionInfo={ transactionInfo }
show={ showTransaction }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailTransactionInfo>
<div className="txn-divider"></div>
<DetailSettlementInfo
pageType={ PageType.Escro }
settlementInfo={ settlementInfo }
show={ showSettlement }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailSettlementInfo>
<div className="txn-divider"></div>
<DetailPartCancelInfo
pageType={ PageType.Escro }
partCancelInfo={ partCancelInfo }
show={ showPartCancel }
onClickToShowInfo={ (info) => onClickToShowInfo(info) }
></DetailPartCancelInfo>
</div>
</div>
<div className="apply-row">
<button
className="btn-50 btn-blue flex-1"
onClick={ () => onClickToCancel() }
> </button>
</div>
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,186 @@
import moment from 'moment';
import { useEffect, useState } from 'react';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { EscroList } from '@/entities/transaction/ui/escro-list';
import { ListItem, PageType, SortByKeys } from '@/entities/transaction/model/types';
import { useEscroListMutation } from '@/entities/transaction/api/use-escro-list-mutation';
import { useDownloadExcelMutation } from '@/entities/transaction/api/use-download-excel-mutation';
import { DEFAULT_PAGE_PARAM } from '@/entities/common/model/constants';
import { Filter } from '@/entities/transaction/ui/filter';
import { SortOptionsBox } from '@/entities/transaction/ui/sort-options-box';
import { HeaderType } from '@/entities/common/model/types';
import {
useSetOnBack,
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode
} from '@/widgets/sub-layout/use-sub-layout';
const serviceCodes = [
{name: '전체', key: 'all'},
{name: '결제완료', key: 'paid'},
{name: '배송등록', key: 'register'},
];
export const EscroListPage = () => {
const { navigate } = useNavigate();
const [selectedServiceCode, setSelectedServiceCode] = useState<string>('all');
const [sortBy, setSortBy] = useState<SortByKeys>(SortByKeys.New);
const [listItems, setListItems] = useState({});
const [filterOn, setFilterOn] = useState<boolean>(false);
const [pageParam, setPageParam] = useState(DEFAULT_PAGE_PARAM);
const [fromDate, setFromDate] = useState(moment().subtract(1, 'month').format('YYYYMMDD'));
const [toDate, setToDate] = useState(moment().format('YYYYMMDD'));
useSetHeaderTitle('에스크로');
useSetHeaderType(HeaderType.LeftArrow);
useSetOnBack(() => {
navigate(PATHS.home);
});
useSetFooterMode(true);
const { mutateAsync: escroList } = useEscroListMutation();
const { mutateAsync: downloadExcel } = useDownloadExcelMutation();
const callList = (option?: {
sortBy?: string,
val?: string
}) => {
let listSummaryParams = {
moid: 'string',
tid: 'string',
fromDate: fromDate,
toDate: toDate,
stateCode: '0',
serviceCode: (option?.val)? option.val: selectedServiceCode,
minAmount: 0,
maxAmount: 0,
dateCl: 'TRANS',
goodsName: 'string',
cardCode: 'st',
bankCode: 'str',
searchCl: 'CARD_NO',
searchValue: 'string',
};
pageParam.sortBy = (option?.sortBy)? option.sortBy: sortBy;
setPageParam(pageParam);
let listParam = {
...listSummaryParams,
...{page: pageParam}
};
escroList(listParam).then((rs) => {
setListItems(assembleData(rs.content));
});
};
const assembleData = (content: Array<ListItem>) => {
let data: any = {};
if(content && content.length > 0){
for(let i=0;i<content?.length;i++){
let stateDate = content[i]?.stateDate;
let groupDate = stateDate?.substring(0, 8);
if(!!groupDate && !data.hasOwnProperty(groupDate)){
data[groupDate] = [];
}
if(!!groupDate && data.hasOwnProperty(groupDate)){
data[groupDate].push(content[i]);
}
}
}
return data;
};
const onClickToOpenFilter = () => {
setFilterOn(!filterOn);
};
const onClickToDownloadExcel = () => {
// tid??? 확인 필요
downloadExcel({
// tid: tid
}).then((rs) => {
});
};
const onCliCkToSort = (sort: SortByKeys) => {
setSortBy(sort);
callList({sortBy: sort});
};
const onClickToServiceCode = (val: string) => {
setSelectedServiceCode(val);
callList({val: val});
};
useEffect(() => {
callList();
}, []);
return (
<>
<main>
<div className="tab-content">
<div className="tab-pane sub active" id="tab1">
<div className="summary-section">
<div className="credit-controls">
<div>
<input
type="text"
className="credit-period"
value={ moment(fromDate).format('YYYY.MM.DD') + '-' + moment(toDate).format('YYYY.MM.DD') }
readOnly={ true }
/>
<button className="filter-btn">
<img
src={ IMAGE_ROOT + '/ico_setting.svg' }
alt="검색옵션"
onClick={ () => onClickToOpenFilter() }
/>
</button>
</div>
<button className="download-btn">
<img
src={ IMAGE_ROOT + '/ico_download.svg' }
alt="다운로드"
onClick={ () => onClickToDownloadExcel() }
/>
</button>
</div>
</div>
<div className="filter-section">
<SortOptionsBox
sortBy={ sortBy }
onCliCkToSort={ onCliCkToSort }
></SortOptionsBox>
<div className="excrow">
<div className="full-menu-keywords no-padding">
{
serviceCodes.map((value, index) => (
<span
key={ `key-service-code=${ index }` }
className={ `keyword-tag ${(selectedServiceCode === value.key)? 'active': ''}` }
onClick={ () => onClickToServiceCode(value.key) }
>{ value.name }</span>
))
}
</div>
</div>
</div>
<EscroList
listItems={ listItems }
pageType={ PageType.Escro }
></EscroList>
</div>
</div>
</main>
<Filter
filterOn={ filterOn }
setFilterOn={ setFilterOn }
></Filter>
</>
);
};

View File

@@ -0,0 +1,43 @@
import { Route } from 'react-router-dom';
import { SentryRoutes } from '@/shared/configs/sentry';
import { ROUTE_NAMES } from '@/shared/constants/route-names';
import { AllTransactionListPage } from './all-transaction/list-page';
import { AllTransactionDetailPage } from './all-transaction/detail-page';
import { AllTransactionCancelPage } from './all-transaction/cancel-page';
import { CashReceitListPage } from './cash-receit/list-page';
import { CashReceitDetailPage } from './cash-receit/detail-page';
import { CashReceitHandWrittenIssuancePage } from './cash-receit/hand-written-issuance-page';
import { EscroListPage } from './escro/list-page';
import { EscroDetailPage } from './escro/detail-page';
import { BillingListPage } from './billing/list-page';
import { BillingDetailPage } from './billing/detail-page';
import { BillingPaymentRequestPage } from './billing/payment-request-page';
export const TransactionPages = () => {
return (
<>
<SentryRoutes>
<Route path={ROUTE_NAMES.transaction.allTransaction.base}>
<Route path={ROUTE_NAMES.transaction.allTransaction.list} element={<AllTransactionListPage />} />
<Route path={ROUTE_NAMES.transaction.allTransaction.detail} element={<AllTransactionDetailPage />} />
<Route path={ROUTE_NAMES.transaction.allTransaction.cancel} element={<AllTransactionCancelPage />} />
</Route>
<Route path={ROUTE_NAMES.transaction.cashReceit.base}>
<Route path={ROUTE_NAMES.transaction.cashReceit.list} element={<CashReceitListPage />} />
<Route path={ROUTE_NAMES.transaction.cashReceit.detail} element={<CashReceitDetailPage />} />
<Route path={ROUTE_NAMES.transaction.cashReceit.handWrittenIssuance} element={<CashReceitHandWrittenIssuancePage />} />
</Route>
<Route path={ROUTE_NAMES.transaction.escro.base}>
<Route path={ROUTE_NAMES.transaction.escro.list} element={<EscroListPage />} />
<Route path={ROUTE_NAMES.transaction.escro.detail} element={<EscroDetailPage />} />
</Route>
<Route path={ROUTE_NAMES.transaction.billing.base}>
<Route path={ROUTE_NAMES.transaction.billing.list} element={<BillingListPage />} />
<Route path={ROUTE_NAMES.transaction.billing.detail} element={<BillingDetailPage />} />
<Route path={ROUTE_NAMES.transaction.billing.paymentRequest} element={<BillingPaymentRequestPage />} />
</Route>
</SentryRoutes>
</>
);
};