"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";
// downloadFile은 동적으로 import
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/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) => (
))}
);
};
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 && (
)}
);
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 [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) {
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) {
files.push(...answer.uploadedFiles);
}
}
}
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 간격으로 순차 다운로드
});
};
if (!initialData) {
return (
);
}
// attachmentsByType는 상태로 관리되고 있으므로 제거
return (
협력업체 기본정보
{editMode ? (
<>
>
) : (
<>
>
)}
{/* 업체정보 */}
updateField("vendorName", value)}
/>
{/* */}
updateField("phone", value)}
/>
{/* updateField("fax", value)}
/> */}
updateField("businessType", value)}
/>
{/* handleFileManagement("소개자료")}
/> */}
}
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: 조직도 업데이트 로직 구현
}}
/>
관련 첨부파일
}
/>
{/* */}
{/* 매출정보 */}
기준일
자산 구성
영업이익
(백만원)
당기순이익
(백만원)
부채비율
(%)
차입금의존도
(%)
영업이익률
(%)
순이익률
(%)
매출액증감
(%)
유동비율
(%)
총자산
부채총계
자본총계
{["20231231", "20221231", "20211231"].map((dateKey) => {
const year = dateKey;
const salesData = initialData.salesInfo?.[year];
const metricsData = initialData.calculatedMetrics?.[dateKey];
return (
{year}
{salesData
? (
parseInt(salesData.totalDebt.replace(/,/g, "")) +
parseInt(salesData.totalEquity.replace(/,/g, ""))
).toLocaleString()
: "-"}
{salesData?.totalDebt || "-"}
{salesData?.totalEquity || "-"}
{salesData?.operatingProfit || "-"}
{salesData?.netIncome || "-"}
{metricsData?.debtRatio?.toFixed(1) || "-"}
{metricsData?.borrowingDependency?.toFixed(1) || "-"}
{metricsData?.operatingMargin?.toFixed(1) || "-"}
{metricsData?.netMargin?.toFixed(1) || "-"}
{metricsData?.salesGrowth?.toFixed(1) || "-"}
{metricsData?.currentRatio?.toFixed(1) || "-"}
);
})}
}
/>
{/* */}
{/* 실사정보 */}
{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" />
}
/>
);
}