summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/[lng]/admin/ecc/page.tsx706
1 files changed, 706 insertions, 0 deletions
diff --git a/app/[lng]/admin/ecc/page.tsx b/app/[lng]/admin/ecc/page.tsx
new file mode 100644
index 00000000..bb3168c4
--- /dev/null
+++ b/app/[lng]/admin/ecc/page.tsx
@@ -0,0 +1,706 @@
+'use client'
+
+import { useState } from 'react'
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
+import { Button } from '@/components/ui/button'
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+
+import { Badge } from '@/components/ui/badge'
+import { Separator } from '@/components/ui/separator'
+import { Alert, AlertDescription } from '@/components/ui/alert'
+import { Loader2, Play, CheckCircle, XCircle, Send } from 'lucide-react'
+import { toast } from 'sonner'
+
+// SOAP 송신 함수들 import
+import { confirmTestPCR, confirmPCR } from '@/lib/soap/ecc/send/pcr-confirm'
+import { cancelTestRFQ, cancelRFQ } from '@/lib/soap/ecc/send/cancel-rfq'
+import { sendTestRFQInformation, sendRFQInformation } from '@/lib/soap/ecc/send/rfq-info'
+import { createTestPurchaseOrder, createPurchaseOrder } from '@/lib/soap/ecc/send/create-po'
+
+interface TestResult {
+ success: boolean
+ message: string
+ responseData?: string
+ timestamp: string
+ duration?: number
+}
+
+export default function ECCSenderTestPage() {
+ const [isLoading, setIsLoading] = useState<{ [key: string]: boolean }>({})
+ const [testResults, setTestResults] = useState<{ [key: string]: TestResult }>({})
+
+ // 테스트 실행 공통 함수
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const runTest = async (testName: string, testFunction: () => Promise<any>) => {
+ setIsLoading(prev => ({ ...prev, [testName]: true }))
+ const startTime = Date.now()
+
+ try {
+ const result = await testFunction()
+ const duration = Date.now() - startTime
+
+ const testResult: TestResult = {
+ success: result.success,
+ message: result.message,
+ responseData: result.responseData,
+ timestamp: new Date().toLocaleString('ko-KR'),
+ duration
+ }
+
+ setTestResults(prev => ({ ...prev, [testName]: testResult }))
+
+ if (result.success) {
+ toast.success(`${testName} 테스트 성공`)
+ } else {
+ toast.error(`${testName} 테스트 실패: ${result.message}`)
+ }
+ } catch (error) {
+ const testResult: TestResult = {
+ success: false,
+ message: error instanceof Error ? error.message : '알 수 없는 오류',
+ timestamp: new Date().toLocaleString('ko-KR'),
+ duration: Date.now() - startTime
+ }
+
+ setTestResults(prev => ({ ...prev, [testName]: testResult }))
+ toast.error(`${testName} 테스트 오류: ${testResult.message}`)
+ } finally {
+ setIsLoading(prev => ({ ...prev, [testName]: false }))
+ }
+ }
+
+ // PCR 확인 테스트
+ const [pcrData, setPcrData] = useState({
+ PCR_REQ: 'TEST_PCR01',
+ PCR_REQ_SEQ: '00001',
+ PCR_DEC_DATE: '20241201',
+ EBELN: 'TEST_PO01',
+ EBELP: '00010',
+ PCR_STATUS: 'A',
+ WAERS: 'KRW',
+ PCR_NETPR: '1000.00',
+ PEINH: '1',
+ PCR_NETWR: '1000.00',
+ CONFIRM_CD: 'CONF',
+ CONFIRM_RSN: '테스트 확인'
+ })
+
+ // RFQ 취소 테스트
+ const [rfqCancelData, setRfqCancelData] = useState({
+ ANFNR: 'TEST_RFQ_001'
+ })
+
+ // RFQ 정보 전송 테스트
+ const [rfqInfoData, setRfqInfoData] = useState({
+ // 헤더 정보
+ ANFNR: 'RFQ0000001',
+ LIFNR: '1000000001',
+ WAERS: 'KRW',
+ ZTERM: '0001',
+ INCO1: 'FOB',
+ INCO2: 'Seoul, Korea',
+ MWSKZ: 'V0',
+ LANDS: 'KR',
+ VSTEL: '001',
+ LSTEL: '001',
+ // 아이템 정보
+ ANFPS: '00001',
+ NETPR: '1000.00',
+ NETWR: '1000.00',
+ BRTWR: '1100.00',
+ LFDAT: '20241201'
+ })
+
+ // PO 생성 테스트
+ const [poData, setPoData] = useState({
+ // 헤더 정보
+ ANFNR: 'TEST001',
+ LIFNR: '1000000001',
+ ZPROC_IND: 'A',
+ ANGNR: 'TEST001',
+ WAERS: 'KRW',
+ ZTERM: '0001',
+ INCO1: 'FOB',
+ INCO2: 'Seoul, Korea',
+ MWSKZ: 'V0',
+ LANDS: 'KR',
+ ZRCV_DT: '20241201',
+ ZATTEN_IND: 'Y',
+ IHRAN: '20241201',
+ TEXT: 'Test PO Creation',
+ // 아이템 정보
+ ANFPS: '00001',
+ NETPR: '1000.00',
+ PEINH: '1',
+ BPRME: 'EA',
+ NETWR: '1000.00',
+ BRTWR: '1100.00',
+ LFDAT: '20241201',
+ // PR 반환 정보
+ EBELN: 'PR001',
+ EBELP: '00001',
+ MSGTY: 'S',
+ MSGTXT: 'Test message'
+ })
+
+ return (
+ <div className="container mx-auto py-8 space-y-6">
+ <div className="flex items-center justify-between">
+ <div>
+ <h1 className="text-3xl font-bold">ECC SOAP Sender 테스트</h1>
+ <p className="text-muted-foreground mt-2">
+ 4개의 ECC SOAP 송신 라이브러리를 테스트합니다
+ </p>
+ </div>
+ <Badge variant="outline" className="text-sm">
+ 개발/테스트 환경
+ </Badge>
+ </div>
+
+ <Tabs defaultValue="pcr-confirm" className="w-full">
+ <TabsList className="grid w-full grid-cols-4">
+ <TabsTrigger value="pcr-confirm">PCR 확인</TabsTrigger>
+ <TabsTrigger value="rfq-cancel">RFQ 취소</TabsTrigger>
+ <TabsTrigger value="rfq-info">RFQ 정보</TabsTrigger>
+ <TabsTrigger value="po-create">PO 생성</TabsTrigger>
+ </TabsList>
+
+ {/* PCR 확인 탭 */}
+ <TabsContent value="pcr-confirm" className="space-y-6">
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <Send className="h-5 w-5" />
+ PCR (Price Change Request) 확인
+ </CardTitle>
+ <CardDescription>
+ PCR 확인 요청을 ECC로 전송합니다. (IF_ECC_EVCP_PCR_CONFIRM)
+ </CardDescription>
+ </CardHeader>
+ <CardContent className="space-y-4">
+ <div className="grid grid-cols-2 gap-4">
+ <div className="space-y-2">
+ <Label htmlFor="pcr-req">PCR 요청번호 (필수)</Label>
+ <Input
+ id="pcr-req"
+ value={pcrData.PCR_REQ}
+ onChange={(e) => setPcrData(prev => ({ ...prev, PCR_REQ: e.target.value }))}
+ placeholder="PCR 요청번호 (최대 10자)"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="pcr-seq">PCR 요청순번 (필수)</Label>
+ <Input
+ id="pcr-seq"
+ value={pcrData.PCR_REQ_SEQ}
+ onChange={(e) => setPcrData(prev => ({ ...prev, PCR_REQ_SEQ: e.target.value }))}
+ placeholder="PCR 요청순번 (5자리 숫자)"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="pcr-date">PCR 결정일 (필수)</Label>
+ <Input
+ id="pcr-date"
+ value={pcrData.PCR_DEC_DATE}
+ onChange={(e) => setPcrData(prev => ({ ...prev, PCR_DEC_DATE: e.target.value }))}
+ placeholder="YYYYMMDD 형식"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="ebeln">구매오더 (필수)</Label>
+ <Input
+ id="ebeln"
+ value={pcrData.EBELN}
+ onChange={(e) => setPcrData(prev => ({ ...prev, EBELN: e.target.value }))}
+ placeholder="구매오더 번호"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="ebelp">구매오더 품번 (필수)</Label>
+ <Input
+ id="ebelp"
+ value={pcrData.EBELP}
+ onChange={(e) => setPcrData(prev => ({ ...prev, EBELP: e.target.value }))}
+ placeholder="구매오더 품번"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="pcr-status">PCR 상태 (필수)</Label>
+ <Input
+ id="pcr-status"
+ value={pcrData.PCR_STATUS}
+ onChange={(e) => setPcrData(prev => ({ ...prev, PCR_STATUS: e.target.value }))}
+ placeholder="PCR 상태 (1자)"
+ />
+ </div>
+ </div>
+
+ <Separator />
+
+ <div className="flex gap-4">
+ <Button
+ onClick={() => runTest('PCR 확인 (샘플)', () => confirmTestPCR())}
+ disabled={isLoading['PCR 확인 (샘플)']}
+ variant="outline"
+ >
+ {isLoading['PCR 확인 (샘플)'] && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+ <Play className="mr-2 h-4 w-4" />
+ 샘플 데이터로 테스트
+ </Button>
+ <Button
+ onClick={() => runTest('PCR 확인 (사용자)', () => confirmPCR(pcrData))}
+ disabled={isLoading['PCR 확인 (사용자)']}
+ >
+ {isLoading['PCR 확인 (사용자)'] && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+ <Send className="mr-2 h-4 w-4" />
+ 사용자 데이터로 전송
+ </Button>
+ </div>
+
+ {/* 테스트 결과 표시 */}
+ {(testResults['PCR 확인 (샘플)'] || testResults['PCR 확인 (사용자)']) && (
+ <div className="space-y-2">
+ <h4 className="font-semibold">테스트 결과</h4>
+ {testResults['PCR 확인 (샘플)'] && (
+ <TestResultCard result={testResults['PCR 확인 (샘플)']} title="샘플 테스트" />
+ )}
+ {testResults['PCR 확인 (사용자)'] && (
+ <TestResultCard result={testResults['PCR 확인 (사용자)']} title="사용자 테스트" />
+ )}
+ </div>
+ )}
+ </CardContent>
+ </Card>
+ </TabsContent>
+
+ {/* RFQ 취소 탭 */}
+ <TabsContent value="rfq-cancel" className="space-y-6">
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <Send className="h-5 w-5" />
+ RFQ (Request for Quotation) 취소
+ </CardTitle>
+ <CardDescription>
+ RFQ 취소 요청을 ECC로 전송합니다. (IF_ECC_EVCP_CANCEL_RFQ)
+ </CardDescription>
+ </CardHeader>
+ <CardContent className="space-y-4">
+ <div className="space-y-2">
+ <Label htmlFor="anfnr-cancel">RFQ 번호 (필수)</Label>
+ <Input
+ id="anfnr-cancel"
+ value={rfqCancelData.ANFNR}
+ onChange={(e) => setRfqCancelData(prev => ({ ...prev, ANFNR: e.target.value }))}
+ placeholder="RFQ 번호 (최대 10자)"
+ />
+ </div>
+
+ <Separator />
+
+ <div className="flex gap-4">
+ <Button
+ onClick={() => runTest('RFQ 취소 (샘플)', () => cancelTestRFQ())}
+ disabled={isLoading['RFQ 취소 (샘플)']}
+ variant="outline"
+ >
+ {isLoading['RFQ 취소 (샘플)'] && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+ <Play className="mr-2 h-4 w-4" />
+ 샘플 데이터로 테스트
+ </Button>
+ <Button
+ onClick={() => runTest('RFQ 취소 (사용자)', () => cancelRFQ(rfqCancelData.ANFNR))}
+ disabled={isLoading['RFQ 취소 (사용자)']}
+ variant="destructive"
+ >
+ {isLoading['RFQ 취소 (사용자)'] && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+ <Send className="mr-2 h-4 w-4" />
+ 사용자 데이터로 취소
+ </Button>
+ </div>
+
+ {/* 테스트 결과 표시 */}
+ {(testResults['RFQ 취소 (샘플)'] || testResults['RFQ 취소 (사용자)']) && (
+ <div className="space-y-2">
+ <h4 className="font-semibold">테스트 결과</h4>
+ {testResults['RFQ 취소 (샘플)'] && (
+ <TestResultCard result={testResults['RFQ 취소 (샘플)']} title="샘플 테스트" />
+ )}
+ {testResults['RFQ 취소 (사용자)'] && (
+ <TestResultCard result={testResults['RFQ 취소 (사용자)']} title="사용자 테스트" />
+ )}
+ </div>
+ )}
+ </CardContent>
+ </Card>
+ </TabsContent>
+
+ {/* RFQ 정보 탭 */}
+ <TabsContent value="rfq-info" className="space-y-6">
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <Send className="h-5 w-5" />
+ RFQ 정보 전송
+ </CardTitle>
+ <CardDescription>
+ RFQ 정보를 ECC로 전송합니다. (IF_EVCP_ECC_RFQ_INFORMATION)
+ </CardDescription>
+ </CardHeader>
+ <CardContent className="space-y-4">
+ <div className="space-y-4">
+ <h4 className="font-semibold">RFQ 헤더 정보</h4>
+ <div className="grid grid-cols-2 gap-4">
+ <div className="space-y-2">
+ <Label htmlFor="rfq-anfnr">RFQ 번호 (필수)</Label>
+ <Input
+ id="rfq-anfnr"
+ value={rfqInfoData.ANFNR}
+ onChange={(e) => setRfqInfoData(prev => ({ ...prev, ANFNR: e.target.value }))}
+ placeholder="RFQ 번호"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="rfq-lifnr">공급업체 계정 (필수)</Label>
+ <Input
+ id="rfq-lifnr"
+ value={rfqInfoData.LIFNR}
+ onChange={(e) => setRfqInfoData(prev => ({ ...prev, LIFNR: e.target.value }))}
+ placeholder="공급업체 계정번호"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="rfq-waers">통화 (필수)</Label>
+ <Input
+ id="rfq-waers"
+ value={rfqInfoData.WAERS}
+ onChange={(e) => setRfqInfoData(prev => ({ ...prev, WAERS: e.target.value }))}
+ placeholder="통화 코드 (예: KRW)"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="rfq-zterm">지불조건 (필수)</Label>
+ <Input
+ id="rfq-zterm"
+ value={rfqInfoData.ZTERM}
+ onChange={(e) => setRfqInfoData(prev => ({ ...prev, ZTERM: e.target.value }))}
+ placeholder="지불조건 키"
+ />
+ </div>
+ </div>
+
+ <h4 className="font-semibold">RFQ 아이템 정보</h4>
+ <div className="grid grid-cols-2 gap-4">
+ <div className="space-y-2">
+ <Label htmlFor="rfq-anfps">아이템 번호 (필수)</Label>
+ <Input
+ id="rfq-anfps"
+ value={rfqInfoData.ANFPS}
+ onChange={(e) => setRfqInfoData(prev => ({ ...prev, ANFPS: e.target.value }))}
+ placeholder="RFQ 아이템 번호"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="rfq-netpr">순가격 (필수)</Label>
+ <Input
+ id="rfq-netpr"
+ value={rfqInfoData.NETPR}
+ onChange={(e) => setRfqInfoData(prev => ({ ...prev, NETPR: e.target.value }))}
+ placeholder="순가격"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="rfq-netwr">순주문가격 (필수)</Label>
+ <Input
+ id="rfq-netwr"
+ value={rfqInfoData.NETWR}
+ onChange={(e) => setRfqInfoData(prev => ({ ...prev, NETWR: e.target.value }))}
+ placeholder="순주문가격"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="rfq-brtwr">총주문가격 (필수)</Label>
+ <Input
+ id="rfq-brtwr"
+ value={rfqInfoData.BRTWR}
+ onChange={(e) => setRfqInfoData(prev => ({ ...prev, BRTWR: e.target.value }))}
+ placeholder="총주문가격"
+ />
+ </div>
+ </div>
+ </div>
+
+ <Separator />
+
+ <div className="flex gap-4">
+ <Button
+ onClick={() => runTest('RFQ 정보 (샘플)', () => sendTestRFQInformation())}
+ disabled={isLoading['RFQ 정보 (샘플)']}
+ variant="outline"
+ >
+ {isLoading['RFQ 정보 (샘플)'] && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+ <Play className="mr-2 h-4 w-4" />
+ 샘플 데이터로 테스트
+ </Button>
+ <Button
+ onClick={() => runTest('RFQ 정보 (사용자)', async () => {
+ const rfqRequest = {
+ T_RFQ_HEADER: [{
+ ANFNR: rfqInfoData.ANFNR,
+ LIFNR: rfqInfoData.LIFNR,
+ WAERS: rfqInfoData.WAERS,
+ ZTERM: rfqInfoData.ZTERM,
+ INCO1: rfqInfoData.INCO1,
+ INCO2: rfqInfoData.INCO2,
+ MWSKZ: rfqInfoData.MWSKZ,
+ LANDS: rfqInfoData.LANDS,
+ VSTEL: rfqInfoData.VSTEL,
+ LSTEL: rfqInfoData.LSTEL
+ }],
+ T_RFQ_ITEM: [{
+ ANFNR: rfqInfoData.ANFNR,
+ ANFPS: rfqInfoData.ANFPS,
+ NETPR: rfqInfoData.NETPR,
+ NETWR: rfqInfoData.NETWR,
+ BRTWR: rfqInfoData.BRTWR,
+ LFDAT: rfqInfoData.LFDAT
+ }]
+ }
+ return sendRFQInformation(rfqRequest)
+ })}
+ disabled={isLoading['RFQ 정보 (사용자)']}
+ >
+ {isLoading['RFQ 정보 (사용자)'] && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+ <Send className="mr-2 h-4 w-4" />
+ 사용자 데이터로 전송
+ </Button>
+ </div>
+
+ {/* 테스트 결과 표시 */}
+ {(testResults['RFQ 정보 (샘플)'] || testResults['RFQ 정보 (사용자)']) && (
+ <div className="space-y-2">
+ <h4 className="font-semibold">테스트 결과</h4>
+ {testResults['RFQ 정보 (샘플)'] && (
+ <TestResultCard result={testResults['RFQ 정보 (샘플)']} title="샘플 테스트" />
+ )}
+ {testResults['RFQ 정보 (사용자)'] && (
+ <TestResultCard result={testResults['RFQ 정보 (사용자)']} title="사용자 테스트" />
+ )}
+ </div>
+ )}
+ </CardContent>
+ </Card>
+ </TabsContent>
+
+ {/* PO 생성 탭 */}
+ <TabsContent value="po-create" className="space-y-6">
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <Send className="h-5 w-5" />
+ PO (Purchase Order) 생성
+ </CardTitle>
+ <CardDescription>
+ 구매주문 생성 요청을 ECC로 전송합니다. (IF_ECC_EVCP_PO_CREATE)
+ </CardDescription>
+ </CardHeader>
+ <CardContent className="space-y-4">
+ <div className="space-y-4">
+ <h4 className="font-semibold">PO 헤더 정보</h4>
+ <div className="grid grid-cols-2 gap-4">
+ <div className="space-y-2">
+ <Label htmlFor="po-anfnr">입찰번호 (필수)</Label>
+ <Input
+ id="po-anfnr"
+ value={poData.ANFNR}
+ onChange={(e) => setPoData(prev => ({ ...prev, ANFNR: e.target.value }))}
+ placeholder="입찰번호"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="po-lifnr">공급업체 계정 (필수)</Label>
+ <Input
+ id="po-lifnr"
+ value={poData.LIFNR}
+ onChange={(e) => setPoData(prev => ({ ...prev, LIFNR: e.target.value }))}
+ placeholder="공급업체 계정번호"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="po-zproc">처리상태 (필수)</Label>
+ <Input
+ id="po-zproc"
+ value={poData.ZPROC_IND}
+ onChange={(e) => setPoData(prev => ({ ...prev, ZPROC_IND: e.target.value }))}
+ placeholder="구매처리상태"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="po-waers">통화 (필수)</Label>
+ <Input
+ id="po-waers"
+ value={poData.WAERS}
+ onChange={(e) => setPoData(prev => ({ ...prev, WAERS: e.target.value }))}
+ placeholder="통화 코드"
+ />
+ </div>
+ </div>
+
+ <h4 className="font-semibold">PO 아이템 정보</h4>
+ <div className="grid grid-cols-2 gap-4">
+ <div className="space-y-2">
+ <Label htmlFor="po-anfps">입찰 아이템번호 (필수)</Label>
+ <Input
+ id="po-anfps"
+ value={poData.ANFPS}
+ onChange={(e) => setPoData(prev => ({ ...prev, ANFPS: e.target.value }))}
+ placeholder="입찰 아이템번호"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="po-netpr">순가격 (필수)</Label>
+ <Input
+ id="po-netpr"
+ value={poData.NETPR}
+ onChange={(e) => setPoData(prev => ({ ...prev, NETPR: e.target.value }))}
+ placeholder="순가격"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="po-bprme">주문단위 (필수)</Label>
+ <Input
+ id="po-bprme"
+ value={poData.BPRME}
+ onChange={(e) => setPoData(prev => ({ ...prev, BPRME: e.target.value }))}
+ placeholder="주문단위 (예: EA)"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="po-lfdat">납기일 (필수)</Label>
+ <Input
+ id="po-lfdat"
+ value={poData.LFDAT}
+ onChange={(e) => setPoData(prev => ({ ...prev, LFDAT: e.target.value }))}
+ placeholder="YYYYMMDD 형식"
+ />
+ </div>
+ </div>
+ </div>
+
+ <Separator />
+
+ <div className="flex gap-4">
+ <Button
+ onClick={() => runTest('PO 생성 (샘플)', () => createTestPurchaseOrder())}
+ disabled={isLoading['PO 생성 (샘플)']}
+ variant="outline"
+ >
+ {isLoading['PO 생성 (샘플)'] && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+ <Play className="mr-2 h-4 w-4" />
+ 샘플 데이터로 테스트
+ </Button>
+ <Button
+ onClick={() => runTest('PO 생성 (사용자)', async () => {
+ const poRequest = {
+ T_Bidding_HEADER: [{
+ ANFNR: poData.ANFNR,
+ LIFNR: poData.LIFNR,
+ ZPROC_IND: poData.ZPROC_IND,
+ ANGNR: poData.ANGNR,
+ WAERS: poData.WAERS,
+ ZTERM: poData.ZTERM,
+ INCO1: poData.INCO1,
+ INCO2: poData.INCO2,
+ MWSKZ: poData.MWSKZ,
+ LANDS: poData.LANDS,
+ ZRCV_DT: poData.ZRCV_DT,
+ ZATTEN_IND: poData.ZATTEN_IND,
+ IHRAN: poData.IHRAN,
+ TEXT: poData.TEXT
+ }],
+ T_Bidding_ITEM: [{
+ ANFNR: poData.ANFNR,
+ ANFPS: poData.ANFPS,
+ LIFNR: poData.LIFNR,
+ NETPR: poData.NETPR,
+ PEINH: poData.PEINH,
+ BPRME: poData.BPRME,
+ NETWR: poData.NETWR,
+ BRTWR: poData.BRTWR,
+ LFDAT: poData.LFDAT
+ }],
+ T_PR_RETURN: [{
+ ANFNR: poData.ANFNR,
+ ANFPS: poData.ANFPS,
+ EBELN: poData.EBELN,
+ EBELP: poData.EBELP,
+ MSGTY: poData.MSGTY,
+ MSGTXT: poData.MSGTXT
+ }]
+ }
+ return createPurchaseOrder(poRequest)
+ })}
+ disabled={isLoading['PO 생성 (사용자)']}
+ >
+ {isLoading['PO 생성 (사용자)'] && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+ <Send className="mr-2 h-4 w-4" />
+ 사용자 데이터로 생성
+ </Button>
+ </div>
+
+ {/* 테스트 결과 표시 */}
+ {(testResults['PO 생성 (샘플)'] || testResults['PO 생성 (사용자)']) && (
+ <div className="space-y-2">
+ <h4 className="font-semibold">테스트 결과</h4>
+ {testResults['PO 생성 (샘플)'] && (
+ <TestResultCard result={testResults['PO 생성 (샘플)']} title="샘플 테스트" />
+ )}
+ {testResults['PO 생성 (사용자)'] && (
+ <TestResultCard result={testResults['PO 생성 (사용자)']} title="사용자 테스트" />
+ )}
+ </div>
+ )}
+ </CardContent>
+ </Card>
+ </TabsContent>
+ </Tabs>
+ </div>
+ )
+}
+
+// 테스트 결과 표시 컴포넌트
+function TestResultCard({ result, title }: { result: TestResult; title: string }) {
+ return (
+ <Alert className={result.success ? 'border-green-200 bg-green-50' : 'border-red-200 bg-red-50'}>
+ <div className="flex items-center gap-2">
+ {result.success ? (
+ <CheckCircle className="h-4 w-4 text-green-600" />
+ ) : (
+ <XCircle className="h-4 w-4 text-red-600" />
+ )}
+ <span className="font-semibold">{title}</span>
+ <Badge variant="outline" className="text-xs">
+ {result.duration}ms
+ </Badge>
+ <span className="text-xs text-muted-foreground ml-auto">
+ {result.timestamp}
+ </span>
+ </div>
+ <AlertDescription className="mt-2">
+ <div className="space-y-2">
+ <p>{result.message}</p>
+ {result.responseData && (
+ <details className="text-xs">
+ <summary className="cursor-pointer font-medium">응답 데이터 보기</summary>
+ <pre className="mt-2 p-2 bg-gray-100 rounded text-xs overflow-auto max-h-40">
+ {result.responseData}
+ </pre>
+ </details>
+ )}
+ </div>
+ </AlertDescription>
+ </Alert>
+ )
+}