"use client" import * as React from "react" import { zodResolver } from "@hookform/resolvers/zod" import { useForm, useFieldArray } from "react-hook-form" import { useRouter, useParams } from "next/navigation" import { useSession } from "next-auth/react" import i18nIsoCountries from "i18n-iso-countries" import enLocale from "i18n-iso-countries/langs/en.json" import koLocale from "i18n-iso-countries/langs/ko.json" import { Button } from "@/components/ui/button" import { Separator } from "@/components/ui/separator" import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { toast } from "@/hooks/use-toast" import { Popover, PopoverTrigger, PopoverContent, } from "@/components/ui/popover" import { Command, CommandList, CommandInput, CommandEmpty, CommandGroup, CommandItem, } from "@/components/ui/command" import { Check, ChevronsUpDown, Download, Loader2, Plus, X, FileText, Eye, Upload, CheckCircle } from "lucide-react" import { cn } from "@/lib/utils" import { useTranslation } from "@/i18n/client" import { getVendorDetailById, downloadVendorAttachments, updateVendorInfo } from "@/lib/vendors/service" import { updateVendorSchema, updateVendorSchemaWithConditions, type UpdateVendorInfoSchema } from "@/lib/vendors/validations" import { fetchVendorRegistrationStatus } from "@/lib/vendor-regular-registrations/service" import { DocumentStatusDialog } from "@/components/vendor-regular-registrations/document-status-dialog" import { AdditionalInfoDialog } from "@/components/vendor-regular-registrations/additional-info-dialog" import type { VendorRegularRegistration } from "@/config/vendorRegularRegistrationsColumnsConfig" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Dropzone, DropzoneZone, DropzoneInput, DropzoneUploadIcon, DropzoneTitle, DropzoneDescription, } from "@/components/ui/dropzone" import { FileList, FileListItem, FileListHeader, FileListIcon, FileListInfo, FileListName, FileListDescription, FileListAction, } from "@/components/ui/file-list" import { Badge } from "@/components/ui/badge" import { ScrollArea } from "@/components/ui/scroll-area" import prettyBytes from "pretty-bytes" import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card" import { InformationButton } from "@/components/information/information-button" // 보안 파일 다운로드 유틸리티 import import { downloadFile, quickDownload, smartFileAction, getFileInfo, formatFileSize, getSecurityInfo } from "@/lib/file-download" i18nIsoCountries.registerLocale(enLocale) i18nIsoCountries.registerLocale(koLocale) const locale = "ko" const countryMap = i18nIsoCountries.getNames(locale, { select: "official" }) const countryArray = Object.entries(countryMap).map(([code, label]) => ({ code, label, })) // Example agencies + rating scales const creditAgencies = [ { value: "NICE", label: "NICE평가정보" }, { value: "KIS", label: "KIS (한국신용평가)" }, { value: "KED", label: "KED (한국기업데이터)" }, { value: "SCI", label: "SCI평가정보" }, ] const creditRatingScaleMap: Record = { NICE: ["AAA", "AA", "A", "BBB", "BB", "B", "C", "D"], KIS: ["AAA", "AA+", "AA", "A+", "A", "BBB+", "BBB", "BB", "B", "C"], KED: ["AAA", "AA", "A", "BBB", "BB", "B", "CCC", "CC", "C", "D"], SCI: ["AAA", "AA+", "AA", "AA-", "A+", "A", "A-", "BBB+", "BBB-", "B"], } const cashFlowRatingScaleMap: Record = { NICE: ["우수", "양호", "보통", "미흡", "불량"], KIS: ["A+", "A", "B+", "B", "C", "D"], KED: ["1등급", "2등급", "3등급", "4등급", "5등급"], SCI: ["Level 1", "Level 2", "Level 3", "Level 4"], } const MAX_FILE_SIZE = 3e9 // 첨부파일 타입 정의 const ATTACHMENT_TYPES = [ { value: "BUSINESS_REGISTRATION", label: "사업자등록증" }, { value: "CREDIT_REPORT", label: "신용평가보고서" }, { value: "BANK_ACCOUNT_COPY", label: "통장사본" }, { value: "ISO_CERTIFICATION", label: "ISO인증서" }, { value: "GENERAL", label: "일반 문서" }, ] as const type AttachmentType = typeof ATTACHMENT_TYPES[number]['value'] // 파일 타입 정의 interface AttachmentFile { id: number fileName: string filePath: string attachmentType: string fileSize?: number } export function InfoForm() { const params = useParams() || {} const lng = params.lng ? String(params.lng) : "ko" const { t } = useTranslation(lng, "translation") const router = useRouter() const { data: session } = useSession() const companyId = session?.user?.companyId || "17" // 협력업체 데이터 상태 const [vendor, setVendor] = React.useState(null) const [isLoading, setIsLoading] = React.useState(true) const [isSubmitting, setIsSubmitting] = React.useState(false) // 첨부파일 상태 const [existingFiles, setExistingFiles] = React.useState([]) const [existingCreditFiles, setExistingCreditFiles] = React.useState([]) const [existingCashFlowFiles, setExistingCashFlowFiles] = React.useState([]) const [existingSignatureFiles, setExistingSignatureFiles] = React.useState([]) const [selectedFiles, setSelectedFiles] = React.useState([]) const [creditRatingFile, setCreditRatingFile] = React.useState([]) const [cashFlowRatingFile, setCashFlowRatingFile] = React.useState([]) const [isDownloading, setIsDownloading] = React.useState(false); // 정규등록 관련 상태 const [registrationData, setRegistrationData] = React.useState(null) const [documentDialogOpen, setDocumentDialogOpen] = React.useState(false) const [additionalInfoDialogOpen, setAdditionalInfoDialogOpen] = React.useState(false) // 첨부파일 타입 선택 상태 const [selectedAttachmentType, setSelectedAttachmentType] = React.useState("GENERAL") // 서명/직인 업로드 관련 상태 const [signatureFiles, setSignatureFiles] = React.useState([]) const [hasSignature, setHasSignature] = React.useState(false) // React Hook Form const form = useForm({ resolver: zodResolver(updateVendorSchemaWithConditions), defaultValues: { vendorName: "", taxId: "", address: "", email: "", phone: "", country: "", website: "", representativeName: "", representativeBirth: "", representativeEmail: "", representativePhone: "", corporateRegistrationNumber: "", creditAgency: "", creditRating: "", cashFlowRating: "", attachedFiles: undefined, creditRatingAttachment: undefined, cashFlowRatingAttachment: undefined, contacts: [ { contactName: "", contactPosition: "", contactEmail: "", contactPhone: "", }, ], }, mode: "onChange", }) const isFormValid = form.formState.isValid // Field array for contacts const { fields: contactFields, append: addContact, remove: removeContact, replace: replaceContacts } = useFieldArray({ control: form.control, name: "contacts", }) // 협력업체 정보 가져오기 React.useEffect(() => { async function fetchVendorData() { if (!companyId) return try { setIsLoading(true) // 협력업체 상세 정보 가져오기 (view 사용) const vendorData = await getVendorDetailById(Number(companyId)) if (!vendorData) { toast({ variant: "destructive", title: "오류", description: "협력업체 정보를 찾을 수 없습니다.", }) return } setVendor(vendorData) // 첨부파일 정보 분류 (view에서 이미 파싱된 attachments 배열 사용) if (vendorData.attachments && Array.isArray(vendorData.attachments)) { // 신용평가 관련 파일들만 따로 분리 const creditFiles = vendorData.attachments.filter( (file: AttachmentFile) => file.attachmentType === "CREDIT_RATING" ) const cashFlowFiles = vendorData.attachments.filter( (file: AttachmentFile) => file.attachmentType === "CASH_FLOW_RATING" ) // 서명/직인 파일들 분리 const signatureFiles = vendorData.attachments.filter( (file: AttachmentFile) => file.attachmentType === "SIGNATURE" || file.attachmentType === "SEAL" ) // 나머지 모든 파일들 (사업자등록증, 신용평가보고서, 통장사본, ISO인증서, 일반문서 등) const otherFiles = vendorData.attachments.filter( (file: AttachmentFile) => file.attachmentType !== "CREDIT_RATING" && file.attachmentType !== "CASH_FLOW_RATING" && file.attachmentType !== "SIGNATURE" && file.attachmentType !== "SEAL" ) setExistingFiles(otherFiles) // 모든 기타 파일들을 일반 첨부파일 섹션에 표시 setExistingCreditFiles(creditFiles) setExistingCashFlowFiles(cashFlowFiles) setExistingSignatureFiles(signatureFiles) // 서명/직인 파일들 } // 폼 기본값 설정 (연락처 포함) const formValues = { vendorName: vendorData.vendorName || "", taxId: vendorData.taxId || "", address: vendorData.address || "", email: vendorData.email || "", phone: vendorData.phone || "", country: vendorData.country || "", website: vendorData.website || "", representativeName: vendorData.representativeName || "", representativeBirth: vendorData.representativeBirth || "", representativeEmail: vendorData.representativeEmail || "", representativePhone: vendorData.representativePhone || "", corporateRegistrationNumber: vendorData.corporateRegistrationNumber || "", creditAgency: vendorData.creditAgency || "", creditRating: vendorData.creditRating || "", cashFlowRating: vendorData.cashFlowRating || "", } form.reset(formValues) // 연락처 필드 업데이트 if (vendorData.contacts && Array.isArray(vendorData.contacts) && vendorData.contacts.length > 0) { const formattedContacts = vendorData.contacts.map((contact: any) => ({ id: contact.id, contactName: contact.contactName || "", contactPosition: contact.contactPosition || "", contactEmail: contact.contactEmail || "", contactPhone: contact.contactPhone || "", isPrimary: contact.isPrimary || false, })) replaceContacts(formattedContacts) } // 정규등록 상태 데이터 로드 (없는 경우 에러가 아님) try { const registrationResult = await fetchVendorRegistrationStatus(Number(companyId)) if (registrationResult.success) { setRegistrationData(registrationResult.data) } else if (registrationResult.noRegistration) { // 정규등록 데이터가 없는 경우는 정상적인 상황 (기존 정규업체 등) console.log("정규등록 데이터 없음 - 기존 정규업체이거나 아직 등록 진행하지 않음") setRegistrationData(null) } else { // 실제 에러인 경우 console.error("정규등록 상태 조회 오류:", registrationResult.error) setRegistrationData(null) } } catch (error) { console.error("정규등록 상태 조회 중 예외 발생:", error) setRegistrationData(null) } } catch (error) { console.error("Error fetching vendor data:", error) toast({ variant: "destructive", title: "데이터 로드 오류", description: "협력업체 정보를 불러오는 중 오류가 발생했습니다.", }) } finally { setIsLoading(false) } } fetchVendorData() }, [companyId, form, replaceContacts]) // 보안 다운로드 유틸리티를 사용한 개별 파일 다운로드 const handleDownloadFile = async (file: AttachmentFile) => { try { setIsDownloading(true); // 파일이 객체인지 ID인지 확인하고 처리 const fileId = typeof file === 'object' ? file.id : file; const fileName = typeof file === 'object' ? file.fileName : `file-${fileId}`; // API 엔드포인트 URL 구성 const downloadUrl = `/api/vendors/attachments/download?id=${fileId}&vendorId=${Number(companyId)}`; // 보안 다운로드 유틸리티 사용 const result = await downloadFile(downloadUrl, fileName, { action: 'download', showToast: false, // 우리가 직접 토스트 관리 onSuccess: (fileName, fileSize) => { const sizeText = fileSize ? ` (${formatFileSize(fileSize)})` : ''; toast({ title: "다운로드 완료", description: `파일 다운로드가 완료되었습니다: ${fileName}${sizeText}`, }); }, onError: (error) => { console.error("Download error:", error); toast({ variant: "destructive", title: "다운로드 오류", description: error || "파일 다운로드 중 오류가 발생했습니다.", }); } }); if (!result.success && result.error) { // 오류 처리는 onError 콜백에서 이미 처리됨 console.error("Download failed:", result.error); } } catch (error) { console.error("Error downloading file:", error); toast({ variant: "destructive", title: "다운로드 오류", description: "파일 다운로드 중 예상치 못한 오류가 발생했습니다.", }); } finally { setIsDownloading(false); } }; // 보안 다운로드 유틸리티를 사용한 전체 파일 다운로드 const handleDownloadAllFiles = async () => { try { setIsDownloading(true); // 전체 파일 다운로드 API 엔드포인트 const downloadUrl = `/api/vendors/attachments/download-all?vendorId=${Number(companyId)}`; const fileName = `vendor-${companyId}-files.zip`; // 보안 다운로드 유틸리티 사용 const result = await downloadFile(downloadUrl, fileName, { action: 'download', showToast: false, // 우리가 직접 토스트 관리 onSuccess: (fileName, fileSize) => { const sizeText = fileSize ? ` (${formatFileSize(fileSize)})` : ''; toast({ title: "전체 다운로드 완료", description: `전체 파일 다운로드가 완료되었습니다: ${fileName}${sizeText}`, }); }, onError: (error) => { console.error("Download all error:", error); toast({ variant: "destructive", title: "다운로드 오류", description: error || "전체 파일 다운로드 중 오류가 발생했습니다.", }); } }); if (!result.success && result.error) { // 오류 처리는 onError 콜백에서 이미 처리됨 console.error("Download all failed:", result.error); } } catch (error) { console.error("Error downloading files:", error); toast({ variant: "destructive", title: "다운로드 오류", description: "전체 파일 다운로드 중 예상치 못한 오류가 발생했습니다.", }); } finally { setIsDownloading(false); } }; // Dropzone handlers const handleDropAccepted = (acceptedFiles: File[]) => { const newFiles = [...selectedFiles, ...acceptedFiles] setSelectedFiles(newFiles) form.setValue("attachedFiles", newFiles, { shouldValidate: true }) } const handleDropRejected = (fileRejections: any[]) => { fileRejections.forEach((rej) => { toast({ variant: "destructive", title: "File Error", description: `${rej.file.name}: ${rej.errors[0]?.message || "Upload failed"}`, }) }) } const removeFile = (index: number) => { const updated = [...selectedFiles] updated.splice(index, 1) setSelectedFiles(updated) form.setValue("attachedFiles", updated, { shouldValidate: true }) } const handleCreditDropAccepted = (acceptedFiles: File[]) => { const newFiles = [...creditRatingFile, ...acceptedFiles] setCreditRatingFile(newFiles) form.setValue("creditRatingAttachment", newFiles, { shouldValidate: true }) } const handleCreditDropRejected = (fileRejections: any[]) => { fileRejections.forEach((rej) => { toast({ variant: "destructive", title: "File Error", description: `${rej.file.name}: ${rej.errors[0]?.message || "Upload failed"}`, }) }) } const removeCreditFile = (index: number) => { const updated = [...creditRatingFile] updated.splice(index, 1) setCreditRatingFile(updated) form.setValue("creditRatingAttachment", updated, { shouldValidate: true }) } const handleCashFlowDropAccepted = (acceptedFiles: File[]) => { const newFiles = [...cashFlowRatingFile, ...acceptedFiles] setCashFlowRatingFile(newFiles) form.setValue("cashFlowRatingAttachment", newFiles, { shouldValidate: true }) } const handleCashFlowDropRejected = (fileRejections: any[]) => { fileRejections.forEach((rej) => { toast({ variant: "destructive", title: "File Error", description: `${rej.file.name}: ${rej.errors[0]?.message || "Upload failed"}`, }) }) } const removeCashFlowFile = (index: number) => { const updated = [...cashFlowRatingFile] updated.splice(index, 1) setCashFlowRatingFile(updated) form.setValue("cashFlowRatingAttachment", updated, { shouldValidate: true }) } // 기존 파일 삭제 (ID 목록 관리) const [filesToDelete, setFilesToDelete] = React.useState([]) const handleDeleteExistingFile = (fileId: number) => { // 삭제할 ID 목록에 추가 setFilesToDelete([...filesToDelete, fileId]) // UI에서 제거 setExistingFiles(existingFiles.filter(file => file.id !== fileId)) setExistingCreditFiles(existingCreditFiles.filter(file => file.id !== fileId)) setExistingCashFlowFiles(existingCashFlowFiles.filter(file => file.id !== fileId)) setExistingSignatureFiles(existingSignatureFiles.filter(file => file.id !== fileId)) toast({ title: "파일 삭제 표시됨", description: "저장 시 파일이 영구적으로 삭제됩니다.", }) } // 서명/직인 업로드 핸들러들 (한 개만 허용) const handleSignatureDropAccepted = (acceptedFiles: File[]) => { // 첫 번째 파일만 사용 (한 개만 허용) const newFile = acceptedFiles[0] if (newFile) { // 기존 서명/직인 파일이 있으면 삭제 목록에 추가 if (existingSignatureFiles.length > 0) { const existingFileId = existingSignatureFiles[0].id setFilesToDelete([...filesToDelete, existingFileId]) setExistingSignatureFiles([]) // UI에서 제거 toast({ title: "서명/직인 교체", description: "기존 서명/직인이 새 파일로 교체됩니다.", }) } setSignatureFiles([newFile]) // 새 파일 설정 setHasSignature(true) if (acceptedFiles.length > 1) { toast({ title: "파일 제한", description: "서명/직인은 한 개만 등록할 수 있습니다. 첫 번째 파일만 선택되었습니다.", }) } } } const handleSignatureDropRejected = (fileRejections: any[]) => { fileRejections.forEach((rej) => { toast({ variant: "destructive", title: "파일 오류", description: `${rej.file.name}: ${rej.errors[0]?.message || "업로드 실패"}`, }) }) } const removeSignatureFile = () => { setSignatureFiles([]) setHasSignature(false) } // 파일 타입 라벨 가져오기 const getAttachmentTypeLabel = (type: string) => { const attachmentType = ATTACHMENT_TYPES.find(t => t.value === type) return attachmentType?.label || type } const handleAdditionalInfoSave = async () => { // 데이터 새로고침 try { const registrationResult = await fetchVendorRegistrationStatus(Number(companyId)) if (registrationResult.success) { setRegistrationData(registrationResult.data) toast({ title: "데이터 새로고침", description: "등록 현황 데이터가 새로고침되었습니다.", }) } else if (registrationResult.noRegistration) { // 정규등록 데이터가 없는 경우는 정상적인 상황 setRegistrationData(null) } else { // 실제 에러인 경우 console.error("정규등록 상태 새로고침 오류:", registrationResult.error) setRegistrationData(null) } } catch (error) { console.error("정규등록 상태 새로고침 중 예외 발생:", error) setRegistrationData(null) } } // Submit async function onSubmit(values: UpdateVendorInfoSchema) { if (!companyId) { toast({ variant: "destructive", title: "오류", description: "회사 ID를 찾을 수 없습니다.", }) return } setIsSubmitting(true) try { const mainFiles = values.attachedFiles ? Array.from(values.attachedFiles as FileList) : [] const creditRatingFiles = values.creditRatingAttachment ? Array.from(values.creditRatingAttachment as FileList) : [] const cashFlowRatingFiles = values.cashFlowRatingAttachment ? Array.from(values.cashFlowRatingAttachment as FileList) : [] const vendorData = { id: Number(companyId), vendorName: values.vendorName, website: values.website, address: values.address, email: values.email, phone: values.phone, country: values.country, representativeName: values.representativeName || "", representativeBirth: values.representativeBirth || "", representativeEmail: values.representativeEmail || "", representativePhone: values.representativePhone || "", corporateRegistrationNumber: values.corporateRegistrationNumber || "", creditAgency: values.creditAgency || "", creditRating: values.creditRating || "", cashFlowRating: values.cashFlowRating || "", } // 서버 액션 직접 호출 (기존 fetch API 호출 대신) const result = await updateVendorInfo({ vendorData, files: mainFiles, creditRatingFiles, cashFlowRatingFiles, signatureFiles, // 서명/직인 파일들 contacts: values.contacts, filesToDelete, // 삭제할 파일 ID 목록 selectedAttachmentType, // 선택된 첨부파일 타입 }) if (!result.error) { toast({ title: "업데이트 완료", description: "회사 정보가 성공적으로 업데이트되었습니다.", }) // 삭제할 파일 목록 초기화 setFilesToDelete([]) // 페이지 새로고침하여 업데이트된 정보 표시 router.refresh() } else { toast({ variant: "destructive", title: "오류", description: result.error || "업데이트에 실패했습니다.", }) } } catch (error: any) { console.error(error) toast({ variant: "destructive", title: "서버 에러", description: error.message || "에러가 발생했습니다.", }) } finally { setIsSubmitting(false) } } if (isLoading) { return (
협력업체 정보를 불러오는 중입니다...
) } // 보안 정보 가져오기 (선택적으로 사용자에게 표시) const securityInfo = getSecurityInfo(); // Render return (

{t("infoForm.title", { defaultValue: "Update Vendor Information", })}

{t("infoForm.description", { defaultValue: "Here you can view and update your company information and attachments.", })}

{vendor?.status && (
{vendor.status}
)} {/* 보안 정보 표시 (선택적) */}

📁 허용 파일 크기: {securityInfo.maxFileSizeFormatted} | 남은 다운로드: {securityInfo.remainingDownloads}/분

{/* 정규업체 등록 현황 섹션 */} {registrationData ? ( 정규업체 등록 현황 정규업체 등록을 위한 현황을 확인하고 관리하세요. {/* 현재 상태 표시 */} {registrationData.registration && (
{registrationData.registration.status === 'under_review' && '검토중'} {registrationData.registration.status === 'approval_ready' && '조건충족'} {registrationData.registration.status === 'in_review' && '정규등록검토'} {registrationData.registration.status === 'completed' && '등록완료'} {registrationData.registration.status === 'pending_approval' && '장기미등록'}
)} {/* 서명/직인 등록 */}

회사 서명/직인 등록

{/* 현재 등록된 서명/직인 파일 표시 (한 개만) */} {(existingSignatureFiles.length > 0 || signatureFiles.length > 0) && (
서명/직인 등록됨
{/* 기존 등록된 서명/직인 (첫 번째만 표시) */} {existingSignatureFiles.length > 0 && signatureFiles.length === 0 && (
{(() => { const file = existingSignatureFiles[0]; const fileInfo = getFileInfo(file.fileName); return (
{fileInfo.icon} {file.fileName}
{getAttachmentTypeLabel(file.attachmentType)} | {file.fileSize ? formatFileSize(file.fileSize) : '크기 정보 없음'}
handleDownloadFile(file)} disabled={isDownloading} > {isDownloading ? : } handleDeleteExistingFile(file.id)}>
); })()}
)} {/* 새로 업로드된 서명/직인 */} {signatureFiles.length > 0 && (
{(() => { const file = signatureFiles[0]; return (
{file.name}
서명/직인 (새 파일) | {prettyBytes(file.size)}
); })()}
)}
)} {/* 서명/직인 업로드 드롭존 */} {({ maxSize }) => (
{existingSignatureFiles.length > 0 || signatureFiles.length > 0 ? "서명/직인 교체" : "서명/직인 업로드" } 한 개 파일만 업로드 가능 {maxSize ? ` | 최대: ${prettyBytes(maxSize)}` : ""}
)}
{/* 액션 버튼들 */}
) : ( // 정규업체 등록 데이터가 없는 경우 (기존 정규업체이거나 아직 등록 진행 안함) 정규업체 등록 현황 현재 정규업체 등록 진행 상황이 없습니다.

이미 정규업체로 등록되어 있거나, 아직 정규업체 등록을 진행하지 않았습니다.

정규업체 등록이 필요한 경우 담당자에게 문의하세요.

)} {/* 첨부파일 요약 카드 - 기존 파일 있는 경우만 표시 */} {(existingFiles.length > 0 || existingCreditFiles.length > 0 || existingCashFlowFiles.length > 0) && ( 첨부파일 요약 현재 등록된 파일 목록입니다. 전체 다운로드하거나 개별 파일을 다운로드할 수 있습니다.
{existingFiles.length > 0 && (

첨부파일

{existingFiles.map((file) => { const fileInfo = getFileInfo(file.fileName); return ( {fileInfo.icon} {file.fileName} {getAttachmentTypeLabel(file.attachmentType)} | {file.fileSize ? formatFileSize(file.fileSize) : '크기 정보 없음'}
handleDownloadFile(file)} disabled={isDownloading} > {isDownloading ? : } handleDeleteExistingFile(file.id)}>
); })}
)} {existingCreditFiles.length > 0 && (

신용평가 첨부파일

{existingCreditFiles.map((file) => { const fileInfo = getFileInfo(file.fileName); return ( {fileInfo.icon} {file.fileName} {getAttachmentTypeLabel(file.attachmentType)} | {file.fileSize ? formatFileSize(file.fileSize) : '크기 정보 없음'}
handleDownloadFile(file)} disabled={isDownloading} > {isDownloading ? : } handleDeleteExistingFile(file.id)}>
); })}
)} {existingCashFlowFiles.length > 0 && (

현금흐름 첨부파일

{existingCashFlowFiles.map((file) => { const fileInfo = getFileInfo(file.fileName); return ( {fileInfo.icon} {file.fileName} {getAttachmentTypeLabel(file.attachmentType)} | {file.fileSize ? formatFileSize(file.fileSize) : '크기 정보 없음'}
handleDownloadFile(file)} disabled={isDownloading} > {isDownloading ? : } handleDeleteExistingFile(file.id)}>
); })}
)}
{(existingFiles.length + existingCreditFiles.length + existingCashFlowFiles.length) > 1 && ( )}
)}

기본 정보

{/* vendorName is required in the schema → show * */} ( 업체명 )} /> {/* taxId - 읽기 전용으로 표시 */} ( 사업자등록번호 )} /> {/* Address */} ( 주소 )} /> ( 대표 전화 )} /> {/* email */} ( 대표 이메일 회사 대표 이메일(관리자 로그인에 사용될 수 있음) )} /> {/* website */} ( 웹사이트 )} /> { const selectedCountry = countryArray.find( (c) => c.code === field.value ) return ( Country No country found. {countryArray.map((country) => ( field.onChange(country.code) } > {country.label} ))} ) }} />
{/* ───────────────────────────────────────── 담당자 정보 (contacts) ───────────────────────────────────────── */}

담당자 정보 (최소 1명)

{contactFields.map((contact, index) => (
{/* contactName → required */} ( 담당자명 )} /> {/* contactPosition → optional */} ( 직급 / 부서 )} /> {/* contactEmail → required */} ( 이메일 )} /> {/* contactPhone → optional */} ( 전화번호 )} />
{/* Remove contact button row */} {contactFields.length > 1 && (
)}
))}
{/* ───────────────────────────────────────── 한국 사업자 (country === "KR") ───────────────────────────────────────── */} {form.watch("country") === "KR" && (

한국 사업자 정보

{/* 대표자 등... all optional or whichever you want * for */}
( 대표자 이름 )} /> ( 대표자 생년월일 )} /> ( 대표자 이메일 )} /> ( 대표자 전화번호 )} /> ( 법인등록번호 )} />
{/* 신용/현금 흐름 */}
{ const agencyValue = field.value return ( 평가사 신용평가 및 현금흐름등급에 사용할 평가사 ) }} /> {form.watch("creditAgency") && (
{/* 신용평가등급 */} { const selectedAgency = form.watch("creditAgency") const ratingScale = creditRatingScaleMap[ selectedAgency as keyof typeof creditRatingScaleMap ] || [] return ( 신용평가등급 ) }} /> {/* 현금흐름등급 */} { const selectedAgency = form.watch("creditAgency") const ratingScale = cashFlowRatingScaleMap[ selectedAgency as keyof typeof cashFlowRatingScaleMap ] || [] return ( 현금흐름등급 ) }} />
)}
{/* Credit/CashFlow Attachments */} {form.watch("creditAgency") && (
( 신용평가등급 첨부 (추가) {({ maxSize }) => (
드래그 또는 클릭 최대: {maxSize ? prettyBytes(maxSize) : "무제한"}
)}
{creditRatingFile.length > 0 && (
{creditRatingFile.map((file, i) => ( {file.name} {prettyBytes(file.size)} removeCreditFile(i)} > ))}
)}
)} /> {/* Cash Flow Attachment */} ( 현금흐름등급 첨부 (추가) {({ maxSize }) => (
드래그 또는 클릭 최대: {maxSize ? prettyBytes(maxSize) : "무제한"}
)}
{cashFlowRatingFile.length > 0 && (
{cashFlowRatingFile.map((file, i) => ( {file.name} {prettyBytes(file.size)} removeCashFlowFile(i)} > ))}
)}
)} />
)}
)} {/* ───────────────────────────────────────── 첨부파일 (사업자등록증 등) - 추가 파일 ───────────────────────────────────────── */}

기타 첨부파일 추가

{/* 첨부파일 타입 선택 */}
( 첨부 파일 (추가) - {getAttachmentTypeLabel(selectedAttachmentType)} {({ maxSize }) => (
파일 업로드 드래그 또는 클릭 {maxSize ? ` (최대: ${prettyBytes(maxSize)})` : null}
)}
{selectedFiles.length > 0 && (
{selectedFiles.map((file, i) => ( {file.name} {getAttachmentTypeLabel(selectedAttachmentType)} | {prettyBytes(file.size)} removeFile(i)}> ))}
)}
)} />
{/* Submit 버튼 */}
{/* 정규등록 관련 다이얼로그들 - 정규등록 데이터가 있을 때만 표시 */} {registrationData && ( <> {/* 문서 현황 Dialog */} {/* 추가정보 입력 Dialog */} )}
) }