@@ -32,8 +37,16 @@ export default async function VendorBasicPage(props: VendorBasicPageProps) {
}
return (
-
-
+
+ {/* eVCP 벤더 정보 */}
+ {evcpVendorDetails && (
+
+ )}
+
+ {/* Oracle 벤더 정보 */}
+ {oracleVendorDetails && (
+
+ )}
)
}
\ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/text-utils.tsx b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/text-utils.tsx
new file mode 100644
index 00000000..a3507dd0
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/text-utils.tsx
@@ -0,0 +1,131 @@
+"use client"
+
+import { useState } from "react"
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
+import { ChevronDown, ChevronUp } from "lucide-react"
+
+export function TruncatedText({
+ text,
+ maxLength = 50,
+ showTooltip = true
+}: {
+ text: string | null
+ maxLength?: number
+ showTooltip?: boolean
+}) {
+ if (!text) return
-
+
+ if (text.length <= maxLength) {
+ return
{text}
+ }
+
+ const truncated = text.slice(0, maxLength) + "..."
+
+ if (!showTooltip) {
+ return
{truncated}
+ }
+
+ return (
+
+
+
+
+ {truncated}
+
+
+
+ {text}
+
+
+
+ )
+}
+
+export function ExpandableText({
+ text,
+ maxLength = 100,
+ className = ""
+}: {
+ text: string | null
+ maxLength?: number
+ className?: string
+}) {
+ const [isExpanded, setIsExpanded] = useState(false)
+
+ if (!text) return
-
+
+ if (text.length <= maxLength) {
+ return
{text}
+ }
+
+ return (
+
+
+
+
+
+
+
+ )
+}
+
+export function AddressDisplay({
+ address,
+ addressEng,
+ postalCode,
+ addressDetail
+}: {
+ address: string | null
+ addressEng: string | null
+ postalCode: string | null
+ addressDetail: string | null
+}) {
+ const hasAnyAddress = address || addressEng || postalCode || addressDetail
+
+ if (!hasAnyAddress) {
+ return
-
+ }
+
+ return (
+
+ {postalCode && (
+
+ 우편번호: {postalCode}
+
+ )}
+ {address && (
+
+ {address}
+
+ )}
+ {addressDetail && (
+
+ {addressDetail}
+
+ )}
+ {addressEng && (
+
+ {addressEng}
+
+ )}
+
+ )
+}
\ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-basic-info.tsx b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-basic-info.tsx
index 16f75bcb..e9cbd8be 100644
--- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-basic-info.tsx
+++ b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-basic-info.tsx
@@ -3,14 +3,13 @@
import { useState, useTransition, useMemo } from "react"
import { useParams } from "next/navigation"
import { toast } from "sonner"
-import { Separator } from "@/components/ui/separator"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
-import { AddressDisplay } from "@/components/ui/text-utils"
+import { AddressDisplay } from "./text-utils"
import {
Dialog,
DialogContent,
@@ -86,7 +85,7 @@ interface VendorBasicInfoProps {
export function VendorBasicInfo({ vendorDetails }: VendorBasicInfoProps) {
const params = useParams()
- const vendorId = params.id as string
+ const vendorId = params?.id as string
const [isEditing, setIsEditing] = useState(false)
const [editData, setEditData] = useState(vendorDetails)
const [isPending, startTransition] = useTransition()
@@ -186,23 +185,23 @@ export function VendorBasicInfo({ vendorDetails }: VendorBasicInfoProps) {
const result = await updateMdgVendorBasicInfo({
vendorId,
updateData: {
- VNDRNM_1: editData.VNDRNM_1,
- VNDRNM_2: editData.VNDRNM_2,
- VNDRNM_ABRV_1: editData.VNDRNM_ABRV_1,
- BIZR_NO: editData.BIZR_NO,
- CO_REG_NO: editData.CO_REG_NO,
- CO_VLM: editData.CO_VLM,
- REPR_NM: editData.REPR_NM,
- REP_TEL_NO: editData.REP_TEL_NO,
- REPR_RESNO: editData.REPR_RESNO,
- REPRESENTATIVE_EMAIL: editData.REPRESENTATIVE_EMAIL,
- BIZTP: editData.BIZTP,
- BIZCON: editData.BIZCON,
- NTN_CD: editData.NTN_CD,
- ADR_1: editData.ADR_1,
- ADR_2: editData.ADR_2,
- POSTAL_CODE: editData.POSTAL_CODE,
- ADDR_DETAIL_1: editData.ADDR_DETAIL_1,
+ VNDRNM_1: editData.VNDRNM_1 || undefined,
+ VNDRNM_2: editData.VNDRNM_2 || undefined,
+ VNDRNM_ABRV_1: editData.VNDRNM_ABRV_1 || undefined,
+ BIZR_NO: editData.BIZR_NO || undefined,
+ CO_REG_NO: editData.CO_REG_NO || undefined,
+ CO_VLM: editData.CO_VLM || undefined,
+ REPR_NM: editData.REPR_NM || undefined,
+ REP_TEL_NO: editData.REP_TEL_NO || undefined,
+ REPR_RESNO: editData.REPR_RESNO || undefined,
+ REPRESENTATIVE_EMAIL: editData.REPRESENTATIVE_EMAIL || undefined,
+ BIZTP: editData.BIZTP || undefined,
+ BIZCON: editData.BIZCON || undefined,
+ NTN_CD: editData.NTN_CD || undefined,
+ ADR_1: editData.ADR_1 || undefined,
+ ADR_2: editData.ADR_2 || undefined,
+ POSTAL_CODE: editData.POSTAL_CODE || undefined,
+ ADDR_DETAIL_1: editData.ADDR_DETAIL_1 || undefined,
}
})
@@ -365,47 +364,56 @@ export function VendorBasicInfo({ vendorDetails }: VendorBasicInfoProps) {
return (
<>
{/* 헤더 */}
-
-
-
- {editData.VNDRNM_1 || '업체명 없음'}
-
-
- 벤더 코드: {editData.VNDRCD}
-
-
-
- {/* 상태 배지 */}
-
- {getStatusBadge(editData.DEL_ORDR)}
+
+
+
+
+
+
+ {editData.VNDRNM_1 || '업체명 없음'}
+ Oracle
+
+
+ 벤더 코드: {editData.VNDRCD}
+
+
+
+ {/* 상태 배지 */}
+
+ {getStatusBadge(editData.DEL_ORDR)}
+
+
+ {/* 액션 버튼들 */}
+
+ {isEditing ? (
+ <>
+
+
+ >
+ ) : (
+ <>
+
+ >
+ )}
+
+
-
- {/* 액션 버튼들 */}
-
- {isEditing ? (
- <>
-
-
- >
- ) : (
- <>
-
- >
- )}
+
+
+
+ Oracle 시스템에서 연동된 협력업체 정보입니다. 수정 시 Oracle에 반영됩니다.
-
-
-
-
+
+
{/* 기본 정보 */}
diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-evcp-info.tsx b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-evcp-info.tsx
new file mode 100644
index 00000000..4da3162a
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-evcp-info.tsx
@@ -0,0 +1,396 @@
+"use client"
+
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { AddressDisplay } from "./text-utils"
+import {
+ Phone,
+ Mail,
+ Calendar,
+ CheckCircle,
+ XCircle,
+ Building2,
+ Globe,
+ User,
+ FileText,
+ TrendingUp,
+ Hash,
+ MapPin,
+ Users,
+ Award,
+ Briefcase,
+ Shield,
+ Star,
+ DollarSign
+} from "lucide-react"
+import { VendorDetailView } from "@/db/schema/vendors"
+
+interface VendorEvCpInfoProps {
+ vendorDetails: VendorDetailView
+}
+
+// 신용평가기관 표시 매핑
+const creditAgencyMap: Record
= {
+ NICE: "NICE평가정보",
+ KIS: "KIS (한국신용평가)",
+ KED: "KED (한국기업데이터)",
+ SCI: "SCI평가정보",
+}
+
+// 사업규모 표시 개선
+const businessSizeMap: Record = {
+ "LARGE": { label: "대기업", color: "text-purple-600" },
+ "MEDIUM": { label: "중견기업", color: "text-blue-600" },
+ "SMALL": { label: "중소기업", color: "text-green-600" },
+ "STARTUP": { label: "스타트업", color: "text-orange-600" },
+}
+
+export function VendorEvCpInfo({ vendorDetails }: VendorEvCpInfoProps) {
+ // 연락처 정보 파싱
+ const contacts = Array.isArray(vendorDetails.contacts)
+ ? vendorDetails.contacts
+ : typeof vendorDetails.contacts === 'string'
+ ? JSON.parse(vendorDetails.contacts || '[]')
+ : []
+
+ // 첨부파일 정보 파싱
+ const attachments = Array.isArray(vendorDetails.attachments)
+ ? vendorDetails.attachments
+ : typeof vendorDetails.attachments === 'string'
+ ? JSON.parse(vendorDetails.attachments || '[]')
+ : []
+
+ // 상태에 따른 뱃지 스타일 결정
+ const getStatusBadge = (status: string) => {
+ switch (status) {
+ case 'ACTIVE':
+ return 활성
+ case 'INACTIVE':
+ return 비활성
+ case 'PENDING_REVIEW':
+ return 검토중
+ case 'APPROVED':
+ return 승인됨
+ case 'BLACKLISTED':
+ return 거래금지
+ default:
+ return {status}
+ }
+ }
+
+ // 신용등급 색상 결정
+ const getCreditRatingColor = (rating: string) => {
+ if (rating?.includes('AAA')) return "text-green-600 bg-green-50"
+ if (rating?.includes('AA')) return "text-blue-600 bg-blue-50"
+ if (rating?.includes('A')) return "text-indigo-600 bg-indigo-50"
+ if (rating?.includes('BBB')) return "text-yellow-600 bg-yellow-50"
+ if (rating?.includes('BB') || rating?.includes('B')) return "text-orange-600 bg-orange-50"
+ return "text-gray-600 bg-gray-50"
+ }
+
+ // 필드 렌더링 헬퍼
+ const renderField = (label: string, value: React.ReactNode, icon?: React.ReactNode) => {
+ if (!value) return null
+ return (
+
+
+
+ {value}
+
+
+ )
+ }
+
+ return (
+
+ {/* 헤더 */}
+
+
+
+
+
+
+ {vendorDetails.vendorName || '업체명 없음'}
+
+ eVCP
+
+
+
+ 벤더 코드: {vendorDetails.vendorCode || 'N/A'}
+ 사업자번호: {vendorDetails.taxId || 'N/A'}
+
+
+
+ {getStatusBadge(vendorDetails.status || 'ACTIVE')}
+
+
+
+
+
+
+ {/* 기본 정보 */}
+
+
+
+ 기본 정보
+
+
+
+ {renderField("업체명", vendorDetails.vendorName)}
+ {renderField("업체 코드", vendorDetails.vendorCode, )}
+ {renderField("사업자등록번호", vendorDetails.taxId, )}
+ {renderField("법인등록번호", vendorDetails.corporateRegistrationNumber, )}
+ {renderField("국가", vendorDetails.country, )}
+
+ {/* 사업규모 */}
+ {vendorDetails.businessSize && (
+
+
+
+
+ {businessSizeMap[vendorDetails.businessSize]?.label || vendorDetails.businessSize}
+
+
+
+ )}
+
+ {/* 등록일 */}
+ {renderField("등록일",
+ vendorDetails.createdAt ? new Date(vendorDetails.createdAt).toLocaleDateString('ko-KR') : null,
+
+ )}
+
+
+
+ {/* 연락처 정보 */}
+
+
+
+ 연락처 정보
+
+
+
+ {renderField("전화번호", vendorDetails.phone, )}
+ {renderField("이메일",
+ vendorDetails.email && (
+
+ {vendorDetails.email}
+
+ ),
+
+ )}
+
+
+
+
+ {vendorDetails.website ? (
+
+ ) : (
+
정보 없음
+ )}
+
+
+
+ {renderField("주소",
+ vendorDetails.address && ,
+
+ )}
+
+
+
+ {/* 대표자 정보 */}
+ {(vendorDetails.representativeName || vendorDetails.representativeEmail || vendorDetails.representativePhone) && (
+
+
+
+ 대표자 정보
+
+
+
+ {renderField("대표자명", vendorDetails.representativeName, )}
+ {renderField("대표자 이메일",
+ vendorDetails.representativeEmail && (
+
+ {vendorDetails.representativeEmail}
+
+ ),
+
+ )}
+ {renderField("대표자 전화번호", vendorDetails.representativePhone, )}
+ {renderField("대표자 생년월일", vendorDetails.representativeBirth, )}
+
+
+ )}
+
+ {/* 신용평가 정보 */}
+ {(vendorDetails.creditAgency || vendorDetails.creditRating || vendorDetails.cashFlowRating) && (
+
+
+
+
+ 신용평가 정보
+
+
+
+ {renderField("신용평가기관",
+ vendorDetails.creditAgency && creditAgencyMap[vendorDetails.creditAgency] || vendorDetails.creditAgency,
+
+ )}
+
+ {vendorDetails.creditRating && (
+
+
+
+
+ {vendorDetails.creditRating}
+
+
+
+ )}
+
+ {vendorDetails.cashFlowRating && (
+
+
+
+
+ {vendorDetails.cashFlowRating}
+
+
+
+ )}
+
+
+ )}
+
+ {/* 제공 서비스/품목 */}
+ {vendorDetails.items && (
+
+
+
+
+ 제공 서비스/품목
+
+
+
+
+ {vendorDetails.items}
+
+
+
+ )}
+
+ {/* 등록된 연락처 */}
+ {contacts.length > 0 && (
+
+
+
+
+ 등록된 연락처 ({contacts.length}개)
+
+
+
+
+ {contacts.map((contact: any, index: number) => (
+
+
+ {contact.contactName}
+ {contact.isPrimary && (
+ 주 담당자
+ )}
+
+ {contact.contactPosition && (
+
+ {contact.contactPosition}
+
+ )}
+
+ {contact.contactEmail && (
+
+ )}
+ {contact.contactPhone && (
+
+
+
{contact.contactPhone}
+
+ )}
+
+
+ ))}
+
+
+
+ )}
+
+ {/* 첨부파일 정보 */}
+ {attachments.length > 0 && (
+
+
+
+
+ 첨부파일 ({attachments.length}개)
+
+
+
+
+ {attachments.map((attachment: any, index: number) => (
+
+
+
+ {attachment.fileName}
+
+
+
+ {attachment.attachmentType || 'GENERAL'}
+
+ {attachment.createdAt && (
+
+ {new Date(attachment.createdAt).toLocaleDateString('ko-KR')}
+
+ )}
+
+
+ ))}
+
+
+
+ )}
+
+
+ )
+}
\ No newline at end of file
diff --git a/db/schema/SOAP/soap.ts b/db/schema/SOAP/soap.ts
index 7a16b50a..c3e7a0b5 100644
--- a/db/schema/SOAP/soap.ts
+++ b/db/schema/SOAP/soap.ts
@@ -8,8 +8,8 @@ export const soapLogs = soapSchema.table("soap_logs", {
direction: varchar({ length: 20 }).notNull(),
system: varchar({ length: 50 }).notNull(),
interface: varchar({ length: 100 }).notNull(),
- startedAt: timestamp().notNull(),
- endedAt: timestamp(),
+ startedAt: timestamp({ withTimezone: true }).notNull(),
+ endedAt: timestamp({ withTimezone: true }),
isSuccess: boolean().default(false).notNull(),
requestData: text(),
responseData: text(),
--
cgit v1.2.3