"use client"; import React, { useState, useTransition } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Separator } from "@/components/ui/separator"; import { Checkbox } from "@/components/ui/checkbox"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Edit, Save, X } from "lucide-react"; import { toast } from "sonner"; import { VendorData, VendorFormData, VendorAttachment } from "./types"; import { updateVendorData, getVendorPQData, getVendorPQSubmissionData, getVendorAdditionalInfo } from "./actions"; import { getVendorBusinessContacts } from "./actions"; import { noDataString } from "./constants"; import { PQSimpleDialog } from "@/components/vendor-info/pq-simple-dialog"; import { SiteVisitDetailDialog } from "@/lib/site-visit/site-visit-detail-dialog"; import { DocumentStatusDialog } from "@/components/vendor-regular-registrations/document-status-dialog"; import { AdditionalInfoDialog } from "@/components/vendor-regular-registrations/additional-info-dialog"; import { getSiteVisitRequestsByVendorId } from "@/lib/site-visit/service"; import { fetchVendorRegistrationStatus } from "@/lib/vendor-regular-registrations/service"; import { getVendorAttachmentsByType, getVendorPeriodicGrade, getVendorTypeInfo } from "@/lib/vendor-info/service"; import { useCreditIntegration } from "./use-credit-integration"; // downloadFile은 동적으로 import import { SalesInfoTable } from "./sales-info-table"; interface BasicInfoClientProps { initialData: VendorData | null; vendorId: string; } interface DropdownOption { value: string; label: string; } interface InfoItemProps { title: string; value: string | boolean; isEditable?: boolean; editMode?: boolean; onChange?: (value: string | boolean) => void; fieldKey?: string; type?: "text" | "checkbox" | "dropdown" | "file-button" | "readonly"; options?: DropdownOption[]; // dropdown용 옵션들 onFileButtonClick?: () => void; // 파일 버튼 클릭 핸들러 placeholder?: string; // input placeholder } const InfoItem = ({ title, value, isEditable = false, editMode = false, onChange, fieldKey, type = "text", options = [], onFileButtonClick, placeholder, }: InfoItemProps) => { // 편집 가능 여부 결정 (readonly 타입은 항상 읽기 전용) const canEdit = isEditable && editMode && type !== "readonly"; // 표시할 값 결정 (빈 값일 때 처리) const displayValue = value || ""; const showNoData = !value && !canEdit; const renderEditableField = () => { switch (type) { case "checkbox": return (
onChange?.(checked)} />
); case "dropdown": return ( ); case "file-button": return ( ); case "text": default: return ( onChange?.(e.target.value)} className="h-8" placeholder={placeholder} /> ); } }; const renderReadOnlyField = () => { switch (type) { case "checkbox": return (
); case "file-button": return ( ); case "dropdown": case "text": case "readonly": default: return showNoData ? noDataString : displayValue; } }; return (
{title}:
{canEdit ? (
{renderEditableField()}
) : ( {renderReadOnlyField()} )}
); }; const OrganizationChart = ({ data, editMode = false, onChange, pqData, }: { data: any; editMode?: boolean; onChange?: (field: string, value: string) => void; pqData: any[]; }) => { const organizationFields = [ { key: "representative", label: "대표", code: "1-5-1" }, { key: "sales", label: "영업", code: "1-5-2" }, { key: "design", label: "설계", code: "1-5-3" }, { key: "procurement", label: "구매", code: "1-5-4" }, { key: "production", label: "생산", code: "1-5-5" }, { key: "quality", label: "품질", code: "1-5-6" }, ]; return (
조직도
{organizationFields.map((field) => (
{field.label}
{editMode ? ( onChange?.(field.key, e.target.value)} className="h-8 w-16 text-center" placeholder="0" /> ) : ( {pqData && Array.isArray(pqData) && pqData.find((a: any) => a.criteriaCode === field.code)?.answer || data?.[field.key]?.toString() || noDataString} )}
))}
); }; const InfoSection = ({ title, subtitle, column1, column2, column3, additionalContent, }: { title: string; subtitle?: string; column1: React.ReactNode; column2: React.ReactNode; column3: React.ReactNode; additionalContent?: React.ReactNode; }) => (
{title}
{subtitle && (
{subtitle}
)}
{column1}
{column2}
{column3}
{additionalContent && (
{additionalContent}
)}
); const WideInfoSection = ({ title, subtitle, content, noPadding = false, }: { title?: string; subtitle?: string; content: React.ReactNode; noPadding?: boolean; }) => (
{title}
{subtitle && (
{subtitle}
)}
{content}
); export default function BasicInfoClient({ initialData, vendorId, }: BasicInfoClientProps) { const [editMode, setEditMode] = useState(false); const [isPending, startTransition] = useTransition(); // 신용평가사 데이터 통합 훅 const { loading: creditLoading, error: creditError, creditResults, selectedCreditService, bestResult, getCurrentResult, handleCreditServiceChange, creditServices, } = useCreditIntegration(vendorId); // 다이얼로그 상태 const [pqDialogOpen, setPqDialogOpen] = useState(false); const [siteVisitDialogOpen, setSiteVisitDialogOpen] = useState(false); const [contractDialogOpen, setContractDialogOpen] = useState(false); const [additionalInfoDialogOpen, setAdditionalInfoDialogOpen] = useState(false); // 각 다이얼로그에 필요한 데이터 상태 const [selectedSiteVisitRequest, setSelectedSiteVisitRequest] = useState(null); const [registrationData, setRegistrationData] = useState(null); // 첨부파일 및 평가 정보 상태 const [attachmentsByType, setAttachmentsByType] = useState>({}); const [periodicGrade, setPeriodicGrade] = useState(null); const [vendorTypeInfo, setVendorTypeInfo] = useState(null); const [pqData, setPqData] = useState([]); const [pqSubmissionData, setPqSubmissionData] = useState([]); const [additionalInfo, setAdditionalInfo] = useState(null); const [businessContacts, setBusinessContacts] = useState([]); const [formData, setFormData] = useState({ vendorName: initialData?.vendorName || "", representativeName: initialData?.representativeName || "", representativeWorkExperience: initialData?.representativeWorkExperience || false, representativeBirth: initialData?.representativeBirth || "", representativePhone: initialData?.representativePhone || "", representativeEmail: initialData?.representativeEmail || "", phone: initialData?.phone || "", fax: initialData?.fax || "", email: initialData?.email || "", address: initialData?.address || "", addressDetail: initialData?.addressDetail || "", postalCode: initialData?.postalCode || "", businessSize: initialData?.businessSize || "", country: initialData?.country || "", website: initialData?.website || "", businessType: initialData?.additionalInfo?.businessType || "", employeeCount: initialData?.additionalInfo?.employeeCount || 0, mainBusiness: initialData?.additionalInfo?.mainBusiness || "", }); const handleSave = () => { // startTransition(async () => { // try { // const result = await updateVendorData(vendorId, formData); // if (result.success) { // toast.success("[개발중] 저장되지 않습니다. 업데이트는 구현중입니다."); // setEditMode(false); // } else { // toast.error(result.message || "저장에 실패했습니다."); // } // } catch { // toast.error("저장 중 오류가 발생했습니다."); // } // }); }; const handleCancel = () => { setFormData({ vendorName: initialData?.vendorName || "", representativeName: initialData?.representativeName || "", representativeWorkExperience: initialData?.representativeWorkExperience || false, representativeBirth: initialData?.representativeBirth || "", representativePhone: initialData?.representativePhone || "", representativeEmail: initialData?.representativeEmail || "", phone: initialData?.phone || "", fax: initialData?.fax || "", email: initialData?.email || "", address: initialData?.address || "", addressDetail: initialData?.addressDetail || "", postalCode: initialData?.postalCode || "", businessSize: initialData?.businessSize || "", country: initialData?.country || "", website: initialData?.website || "", businessType: initialData?.additionalInfo?.businessType || "", employeeCount: initialData?.additionalInfo?.employeeCount || 0, mainBusiness: initialData?.additionalInfo?.mainBusiness || "", }); setEditMode(false); }; const updateField = ( field: keyof VendorFormData, value: string | number | boolean ) => { setFormData((prev) => ({ ...prev, [field]: value })); }; // 기본계약 현황 조회 핸들러 const handleContractView = async () => { try { const result = await fetchVendorRegistrationStatus(parseInt(vendorId)); if (!result.success || !result.data) { toast.info("기본계약 정보가 없습니다."); return; } // DocumentStatusDialog가 기대하는 형태로 데이터 구성 const dialogData = { // 기본 정보 id: result.data.registration?.id || 0, vendorId: parseInt(vendorId), companyName: result.data.vendor.vendorName, businessNumber: result.data.vendor.taxId, representative: result.data.vendor.representativeName, country: result.data.vendor.country, status: result.data.registration?.status || "정보없음", // 문서 제출 현황 - documentSubmissions 속성으로 매핑 documentSubmissions: result.data.documentStatus, // 문서별 파일 정보 추가 documentFiles: result.data.documentFiles || { businessRegistration: [], creditEvaluation: [], bankCopy: [], auditResult: [] }, // 기본계약 정보 basicContracts: result.data.basicContracts || [], // 안전적격성 평가 safetyQualificationContent: result.data.registration?.safetyQualificationContent || null, // 추가정보 완료 여부 additionalInfo: result.data.additionalInfoCompleted, }; setRegistrationData(dialogData); setContractDialogOpen(true); } catch (error) { console.error("기본계약 정보 조회 오류:", error); toast.error("기본계약 정보를 불러오는데 실패했습니다."); } }; // 첨부파일 및 평가 정보 로드 const loadVendorData = async () => { try { // 첨부파일 조회 const attachmentsResult = await getVendorAttachmentsByType(parseInt(vendorId)); if (attachmentsResult.success && attachmentsResult.data) { setAttachmentsByType(attachmentsResult.data); } // 정기평가 등급 조회 const gradeResult = await getVendorPeriodicGrade(parseInt(vendorId)); if (gradeResult.success && gradeResult.data) { setPeriodicGrade(gradeResult.data.finalGrade); } // 벤더 타입 정보 조회 const typeResult = await getVendorTypeInfo(parseInt(vendorId)); if (typeResult.success && typeResult.data) { setVendorTypeInfo(typeResult.data); } // PQ 데이터 조회 const pqResult = await getVendorPQData(vendorId); if (pqResult) { setPqData(pqResult); } // PQ 제출 데이터 조회 const pqSubmissionResult = await getVendorPQSubmissionData(vendorId); if (pqSubmissionResult) { setPqSubmissionData(pqSubmissionResult); } // 추가정보 조회 const additionalInfoResult = await getVendorAdditionalInfo(vendorId); if (additionalInfoResult) { setAdditionalInfo(additionalInfoResult); } // 업무담당자 정보 조회 const contacts = await getVendorBusinessContacts(vendorId); setBusinessContacts(contacts || []); } catch (error) { console.error("벤더 데이터 로드 오류:", error); } }; // 컴포넌트 마운트 시 데이터 로드 React.useEffect(() => { if (vendorId) { loadVendorData(); } }, [vendorId]); // 첨부파일 다운로드 핸들러 const handleAttachmentDownload = async (filePath: string, fileName: string) => { try { // 동적으로 downloadFile 함수 import const { downloadFile } = await import('@/lib/file-download') const result = await downloadFile(filePath, fileName); if (result.success) { toast.success(`${fileName} 파일이 다운로드되었습니다.`); } else { toast.error(result.error || "파일 다운로드에 실패했습니다."); } } catch (error) { console.error("파일 다운로드 오류:", error); toast.error("파일 다운로드에 실패했습니다."); } }; // PQ 데이터에서 실사 정보 추출 const extractFactoryInfo = (pqData: any[]) => { const factoryInfo = { factoryAddress: "", factoryPhone: "", factoryFax: "", factoryPIC: "", factoryPICPosition: "", factoryPICContact: "", factoryPICEmail: "", mainSupplyItems: "", inspectionResult: "", inspectionDate: "", inspectionFiles: [] as any[] }; if (!pqData || !Array.isArray(pqData)) { return factoryInfo; } pqData.forEach(group => { if (group && group.items && Array.isArray(group.items)) { group.items.forEach((item: any) => { const code = item.code; const answer = item.answer; const files = item.uploadedFiles || []; // 공장주소 (1-4-1) if (code === "1-4-1") { factoryInfo.factoryAddress = answer || ""; } // 공장 전화 (1-4-2) else if (code === "1-4-2") { factoryInfo.factoryPhone = answer || ""; } // 공장 팩스 (1-4-3) else if (code === "1-4-3") { factoryInfo.factoryFax = answer || ""; } // 공장 대표/담당자 이름 (1-4-4) else if (code === "1-4-4") { factoryInfo.factoryPIC = answer || ""; } // 공장 대표/담당자 직책 (1-4-5) else if (code === "1-4-5") { factoryInfo.factoryPICPosition = answer || ""; } // 공장 대표/담당자 전화 (1-4-6) else if (code === "1-4-6") { factoryInfo.factoryPICContact = answer || ""; } // 공장 대표/담당자 이메일 (1-4-7) else if (code === "1-4-7") { factoryInfo.factoryPICEmail = answer || ""; } // 공급품목 (첫 번째 것만 가져오기) else if (code.startsWith("1-5") && !factoryInfo.mainSupplyItems) { try { const supplyItems = JSON.parse(answer || "[]"); if (Array.isArray(supplyItems) && supplyItems.length > 0) { factoryInfo.mainSupplyItems = supplyItems[0].name || supplyItems[0] || ""; } } catch { factoryInfo.mainSupplyItems = answer || ""; } } // 실사 결과 else if (code.startsWith("4-") && answer && !factoryInfo.inspectionResult) { factoryInfo.inspectionResult = answer; factoryInfo.inspectionFiles = files; } }); } }); return factoryInfo; }; // PQ 제출 데이터에서 특정 코드의 답변 가져오기 (최신 값 우선) const getPQAnswerByCode = (targetCode: string) => { if (!pqSubmissionData || !Array.isArray(pqSubmissionData)) { return ""; } // 최신 제출부터 순회하면서 값이 있는 첫 번째 답변 반환 for (const submission of pqSubmissionData) { if (submission && submission.answers && Array.isArray(submission.answers)) { const answer = submission.answers.find((a: any) => a.criteriaCode === targetCode); if (answer && answer.answer) { return answer.answer; } } } return ""; }; // PQ 제출 데이터에서 특정 코드의 첨부파일 가져오기 (최신 값 우선) const getPQAttachmentsByCode = (targetCode: string) => { const files: any[] = []; if (!pqSubmissionData || !Array.isArray(pqSubmissionData)) { return files; } // 최신 제출부터 순회하면서 파일이 있는 첫 번째 답변의 파일들 반환 for (const submission of pqSubmissionData) { if (submission && submission.answers && Array.isArray(submission.answers)) { const answer = submission.answers.find((a: any) => a.criteriaCode === targetCode); if (answer && answer.uploadedFiles && answer.uploadedFiles.length > 0) { files.push(...answer.uploadedFiles); break; // 첫 번째로 찾은 파일들만 반환 } } } return files; }; // 첨부파일 관리 핸들러 (타입별) const handleAttachmentFileManagement = (attachmentType: string, typeName: string) => { const files = attachmentsByType[attachmentType] || []; if (files.length === 0) { toast.info(`${typeName} 파일이 없습니다.`); return; } // 파일이 하나인 경우 바로 다운로드 if (files.length === 1) { handleAttachmentDownload(files[0].filePath, files[0].fileName); return; } // 파일이 여러 개인 경우 순차적으로 모든 파일 다운로드 toast.info(`${typeName} 파일 ${files.length}개를 다운로드합니다.`); files.forEach((file, index) => { setTimeout(() => { handleAttachmentDownload(file.filePath, file.fileName); }, index * 500); // 500ms 간격으로 순차 다운로드 }); }; // 소개자료 파일 관리 핸들러 const handleIntroFileManagement = () => { const companyFiles = getPQAttachmentsByCode("1-1"); const productFiles = getPQAttachmentsByCode("1-2"); if (companyFiles.length === 0 && productFiles.length === 0) { toast.info("소개자료 파일이 없습니다."); return; } // 회사 소개자료 다운로드 if (companyFiles.length > 0) { toast.info("회사 소개자료 파일을 다운로드합니다."); companyFiles.forEach((file, index) => { setTimeout(() => { handleAttachmentDownload(file.filePath, file.fileName); }, index * 500); }); } // 제품 소개자료 다운로드 (회사 소개자료 다운로드 완료 후) if (productFiles.length > 0) { setTimeout(() => { toast.info("제품 소개자료 파일을 다운로드합니다."); productFiles.forEach((file, index) => { setTimeout(() => { handleAttachmentDownload(file.filePath, file.fileName); }, index * 500); }); }, companyFiles.length * 500 + 1000); } }; if (!initialData) { return (

{noDataString}

); } // attachmentsByType는 상태로 관리되고 있으므로 제거 return (

협력업체 기본정보

{editMode ? ( <> ) : ( <> )}
{/* 업체정보 */} updateField("vendorName", value)} /> updateField("phone", value)} /> updateField("fax", value)} /> updateField("businessType", value)} />
} column2={
updateField("email", value)} /> {/* updateField("businessSize", value)} placeholder="기업규모를 선택하세요" /> */}
} column3={
updateField("country", value)} /> {/* */}
} /> {/* 첨부파일 */} {/* 사업자등록증 */}
사업자등록증
{attachmentsByType.BUSINESS_REGISTRATION?.length || 0}건
{attachmentsByType.BUSINESS_REGISTRATION?.length > 0 && ( )}
{/* 신용평가보고서 */}
신용평가보고서
{attachmentsByType.CREDIT_REPORT?.length || 0}건
{attachmentsByType.CREDIT_REPORT?.length > 0 && ( )}
{/* 통장사본 */}
통장사본
{attachmentsByType.BANK_ACCOUNT_COPY?.length || 0}건
{attachmentsByType.BANK_ACCOUNT_COPY?.length > 0 && ( )}
{/* ISO 인증서 */}
ISO 인증서
{attachmentsByType.ISO_CERTIFICATION?.length || 0}건
{attachmentsByType.ISO_CERTIFICATION?.length > 0 && ( )}
{/* 기타 첨부파일 (GENERAL) */}
기타 첨부파일
{attachmentsByType.GENERAL?.length || 0}건
{attachmentsByType.GENERAL?.length > 0 && ( )}
} /> {/* 상세정보 */} updateField("representativeName", value)} /> updateField("representativeWorkExperience", value) } /> updateField("representativeBirth", value)} /> } column2={
updateField("representativePhone", value)} /> updateField("address", value)} />
} column3={
updateField("representativeEmail", value)} />
} additionalContent={
s.answers || []) : []} onChange={(field, value) => { // TODO: 조직도 업데이트 로직 구현 }} />
관련 첨부파일
} /> {/* */} {/* 매출정보 */} } /> {/* */} {/* 실사정보 */} {pqSubmissionData && pqSubmissionData.length > 0 && pqSubmissionData.map((submission, index) => { const factoryInfo = extractFactoryInfo([{ groupName: "Factory Info", items: submission.answers.map((answer: any) => ({ code: answer.criteriaCode, answer: answer.answer, uploadedFiles: answer.uploadedFiles || [] })) }]); const inspectionFiles = getPQAttachmentsByCode("4-1"); return ( } column2={
{inspectionFiles.length > 0 && ( )}
} column3={
} additionalContent={
공장소개자료
{getPQAttachmentsByCode("1-4").length}건
{getPQAttachmentsByCode("1-4").length > 0 && ( )}
QMS Cert
{getPQAttachmentsByCode("2-1").length}건
{getPQAttachmentsByCode("2-1").length > 0 && ( )}
Product Cert
{getPQAttachmentsByCode("2-2").length}건
{getPQAttachmentsByCode("2-2").length > 0 && ( )}
Ex. Cert
{getPQAttachmentsByCode("2-17").length}건
{getPQAttachmentsByCode("2-17").length > 0 && ( )}
HSE Cert
{getPQAttachmentsByCode("3-1").length}건
{getPQAttachmentsByCode("3-1").length > 0 && ( )}
} /> ); })} {/* 추가정보 */} } column2={
} column3={
{/* 추가 정보가 더 있다면 여기에 배치 */}
} /> {/* 업무담당자 */}
영업 담당자
c.contactType === "sales")?.contactName || ""} type="readonly" /> c.contactType === "sales")?.position || ""} type="readonly" /> c.contactType === "sales")?.department || ""} type="readonly" /> c.contactType === "sales")?.responsibility || ""} type="readonly" /> c.contactType === "sales")?.email || ""} type="readonly" />
} column2={
설계 담당자
c.contactType === "design")?.contactName || ""} type="readonly" /> c.contactType === "design")?.position || ""} type="readonly" /> c.contactType === "design")?.department || ""} type="readonly" /> c.contactType === "design")?.responsibility || ""} type="readonly" /> c.contactType === "design")?.email || ""} type="readonly" />
납기 담당자
c.contactType === "delivery")?.contactName || ""} type="readonly" /> c.contactType === "delivery")?.position || ""} type="readonly" /> c.contactType === "delivery")?.department || ""} type="readonly" /> c.contactType === "delivery")?.responsibility || ""} type="readonly" /> c.contactType === "delivery")?.email || ""} type="readonly" />
} column3={
품질 담당자
c.contactType === "quality")?.contactName || ""} type="readonly" /> c.contactType === "quality")?.position || ""} type="readonly" /> c.contactType === "quality")?.department || ""} type="readonly" /> c.contactType === "quality")?.responsibility || ""} type="readonly" /> c.contactType === "quality")?.email || ""} type="readonly" />
세금계산서 담당자
c.contactType === "tax_invoice")?.contactName || ""} type="readonly" /> c.contactType === "tax_invoice")?.position || ""} type="readonly" /> c.contactType === "tax_invoice")?.department || ""} type="readonly" /> c.contactType === "tax_invoice")?.responsibility || ""} type="readonly" /> c.contactType === "tax_invoice")?.email || ""} type="readonly" />
} /> ); }