diff options
| author | joonhoekim <26rote@gmail.com> | 2025-09-02 10:15:08 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-09-02 10:15:08 +0000 |
| commit | d5ddafa4276b0031538261400e431009b0734be9 (patch) | |
| tree | 4af37243d10f8c750ca1c2df3843cfca9506fe2e | |
| parent | fccb00d15466cd0b2d861163663a5070c768ff77 (diff) | |
(김준회) 협력업체 기본정보 --> 매출정보 구현사항 통합
| -rw-r--r-- | lib/vendor-basic-info/basic-info-client.tsx | 417 |
1 files changed, 228 insertions, 189 deletions
diff --git a/lib/vendor-basic-info/basic-info-client.tsx b/lib/vendor-basic-info/basic-info-client.tsx index 82133f28..fbf19fe9 100644 --- a/lib/vendor-basic-info/basic-info-client.tsx +++ b/lib/vendor-basic-info/basic-info-client.tsx @@ -341,17 +341,17 @@ export default function BasicInfoClient({ creditServices, reload: reloadCreditData } = 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<any>(null); const [registrationData, setRegistrationData] = useState<any>(null); - + // 첨부파일 및 평가 정보 상태 const [attachmentsByType, setAttachmentsByType] = useState<Record<string, any[]>>({}); const [periodicGrade, setPeriodicGrade] = useState<string | null>(null); @@ -438,7 +438,7 @@ export default function BasicInfoClient({ toast.info("기본계약 정보가 없습니다."); return; } - + // DocumentStatusDialog가 기대하는 형태로 데이터 구성 const dialogData = { // 기본 정보 @@ -449,10 +449,10 @@ export default function BasicInfoClient({ 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: [], @@ -460,17 +460,17 @@ export default function BasicInfoClient({ bankCopy: [], auditResult: [] }, - + // 기본계약 정보 basicContracts: result.data.basicContracts || [], - + // 안전적격성 평가 safetyQualificationContent: result.data.registration?.safetyQualificationContent || null, - + // 추가정보 완료 여부 additionalInfo: result.data.additionalInfoCompleted, }; - + setRegistrationData(dialogData); setContractDialogOpen(true); } catch (error) { @@ -538,7 +538,7 @@ export default function BasicInfoClient({ try { // 동적으로 downloadFile 함수 import const { downloadFile } = await import('@/lib/file-download') - + const result = await downloadFile(filePath, fileName); if (result.success) { toast.success(`${fileName} 파일이 다운로드되었습니다.`); @@ -574,71 +574,72 @@ export default function BasicInfoClient({ 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 || []; + 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] || ""; + // 공장주소 (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 || ""; } - } catch { - factoryInfo.mainSupplyItems = answer || ""; } - } - // 실사 결과 - else if (code.startsWith("4-") && answer && !factoryInfo.inspectionResult) { - factoryInfo.inspectionResult = answer; - factoryInfo.inspectionFiles = files; - } - }); + // 실사 결과 + else if (code.startsWith("4-") && answer && !factoryInfo.inspectionResult) { + factoryInfo.inspectionResult = answer; + factoryInfo.inspectionFiles = files; + } + }); } }); return factoryInfo; }; - // PQ 제출 데이터에서 특정 코드의 답변 가져오기 + // 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) { + if (answer && answer.answer) { return answer.answer; } } @@ -646,30 +647,32 @@ export default function BasicInfoClient({ return ""; }; - // PQ 제출 데이터에서 특정 코드의 첨부파일 가져오기 + // 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) { + 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; @@ -690,6 +693,39 @@ export default function BasicInfoClient({ }); }; + // 소개자료 파일 관리 핸들러 + 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 ( <div className="p-6 bg-background max-w-full"> @@ -762,12 +798,11 @@ export default function BasicInfoClient({ fieldKey="vendorName" onChange={(value) => updateField("vendorName", value)} /> - {/* <InfoItem + <InfoItem title="설립일" - // 현재 필드 없고 linter error 나도 무시. createdAt은 데이터베이스 생성시점이므로 잘못된 필드. - value={initialData.establishmentDate} + value={getPQAnswerByCode("1-6-1") || getPQAnswerByCode("1-6-2") || ""} type="readonly" - /> */} + /> <InfoItem title="대표전화" value={formData.phone} @@ -776,14 +811,14 @@ export default function BasicInfoClient({ fieldKey="phone" onChange={(value) => updateField("phone", value)} /> - {/* <InfoItem + <InfoItem title="팩스" - value={formData.fax} + value={getPQAnswerByCode("1-3-3") || formData.fax || ""} isEditable={true} editMode={editMode} fieldKey="fax" onChange={(value) => updateField("fax", value)} - /> */} + /> <InfoItem title="업체유형" value={formData.businessType} @@ -792,16 +827,15 @@ export default function BasicInfoClient({ fieldKey="businessType" onChange={(value) => updateField("businessType", value)} /> - {/* <InfoItem + <InfoItem title="소개자료" - value={`회사: ${ - attachmentsByType.COMPANY_INTRO?.length || 0 - }건 / 제품: ${attachmentsByType.PRODUCT_INTRO?.length || 0}건`} + value={`회사: ${getPQAttachmentsByCode("1-1").length || 0 + }건 / 제품: ${getPQAttachmentsByCode("1-2").length || 0}건`} isEditable={true} editMode={editMode} type="file-button" - onFileButtonClick={() => handleFileManagement("소개자료")} - /> */} + onFileButtonClick={handleIntroFileManagement} + /> <InfoItem title="정기평가 등급" value={periodicGrade || ""} @@ -866,14 +900,11 @@ export default function BasicInfoClient({ placeholder="기업규모를 선택하세요" /> */} - {/* <InfoItem + <InfoItem title="안전적격성평가" - value={ - initialData.evaluationInfo?.safetyQualificationEvaluation || - null - } + value="" type="readonly" - /> */} + /> </div> } column3={ @@ -883,11 +914,11 @@ export default function BasicInfoClient({ value={vendorTypeInfo?.vendorTypeName || ""} type="readonly" /> - {/* <InfoItem + <InfoItem title="그룹사" - value={initialData.classificationInfo?.groupCompany || null} + value={getPQAnswerByCode("1-6-5") || initialData.classificationInfo?.groupCompany || ""} isEditable={true} - /> */} + /> <InfoItem title="국가" value={formData.country} @@ -903,11 +934,6 @@ export default function BasicInfoClient({ } isEditable={true} /> - {/* <InfoItem - title="산업유형" - value={initialData.classificationInfo?.industryType || ""} - isEditable={true} - /> */} {/* <InfoItem title="당사거래비중" @@ -924,105 +950,95 @@ export default function BasicInfoClient({ <WideInfoSection title="첨부파일" content={ - <div className="grid grid-cols-2 md:grid-cols-5 gap-4 p-4"> + <div className="grid grid-cols-5 gap-4 min-w-0 overflow-x-auto"> {/* 사업자등록증 */} - <div className="flex flex-col items-center gap-3"> - <div className="text-sm font-medium text-center">사업자등록증</div> - <div className="text-center"> - <div className="text-lg font-semibold text-primary"> - {attachmentsByType.BUSINESS_REGISTRATION?.length || 0}건 - </div> - {attachmentsByType.BUSINESS_REGISTRATION?.length > 0 && ( - <Button - variant="outline" - size="sm" - className="mt-2" - onClick={() => handleAttachmentFileManagement("BUSINESS_REGISTRATION", "사업자등록증")} - > - 파일 다운로드 - </Button> - )} + <div className="text-center min-w-0"> + <div className="text-sm font-medium mb-2 break-words">사업자등록증</div> + <div className="text-sm text-muted-foreground"> + {attachmentsByType.BUSINESS_REGISTRATION?.length || 0}건 </div> + {attachmentsByType.BUSINESS_REGISTRATION?.length > 0 && ( + <Button + variant="outline" + size="sm" + className="mt-2" + onClick={() => handleAttachmentFileManagement("BUSINESS_REGISTRATION", "사업자등록증")} + > + 파일 다운로드 + </Button> + )} </div> {/* 신용평가보고서 */} - <div className="flex flex-col items-center gap-3"> - <div className="text-sm font-medium text-center">신용평가보고서</div> - <div className="text-center"> - <div className="text-lg font-semibold text-primary"> - {attachmentsByType.CREDIT_REPORT?.length || 0}건 - </div> - {attachmentsByType.CREDIT_REPORT?.length > 0 && ( - <Button - variant="outline" - size="sm" - className="mt-2" - onClick={() => handleAttachmentFileManagement("CREDIT_REPORT", "신용평가보고서")} - > - 파일 다운로드 - </Button> - )} + <div className="text-center min-w-0"> + <div className="text-sm font-medium mb-2 break-words">신용평가보고서</div> + <div className="text-sm text-muted-foreground"> + {attachmentsByType.CREDIT_REPORT?.length || 0}건 </div> + {attachmentsByType.CREDIT_REPORT?.length > 0 && ( + <Button + variant="outline" + size="sm" + className="mt-2" + onClick={() => handleAttachmentFileManagement("CREDIT_REPORT", "신용평가보고서")} + > + 파일 다운로드 + </Button> + )} </div> {/* 통장사본 */} - <div className="flex flex-col items-center gap-3"> - <div className="text-sm font-medium text-center">통장사본</div> - <div className="text-center"> - <div className="text-lg font-semibold text-primary"> - {attachmentsByType.BANK_ACCOUNT_COPY?.length || 0}건 - </div> - {attachmentsByType.BANK_ACCOUNT_COPY?.length > 0 && ( - <Button - variant="outline" - size="sm" - className="mt-2" - onClick={() => handleAttachmentFileManagement("BANK_ACCOUNT_COPY", "통장사본")} - > - 파일 다운로드 - </Button> - )} + <div className="text-center min-w-0"> + <div className="text-sm font-medium mb-2 break-words">통장사본</div> + <div className="text-sm text-muted-foreground"> + {attachmentsByType.BANK_ACCOUNT_COPY?.length || 0}건 </div> + {attachmentsByType.BANK_ACCOUNT_COPY?.length > 0 && ( + <Button + variant="outline" + size="sm" + className="mt-2" + onClick={() => handleAttachmentFileManagement("BANK_ACCOUNT_COPY", "통장사본")} + > + 파일 다운로드 + </Button> + )} </div> {/* ISO 인증서 */} - <div className="flex flex-col items-center gap-3"> - <div className="text-sm font-medium text-center">ISO 인증서</div> - <div className="text-center"> - <div className="text-lg font-semibold text-primary"> - {attachmentsByType.ISO_CERTIFICATION?.length || 0}건 - </div> - {attachmentsByType.ISO_CERTIFICATION?.length > 0 && ( - <Button - variant="outline" - size="sm" - className="mt-2" - onClick={() => handleAttachmentFileManagement("ISO_CERTIFICATION", "ISO 인증서")} - > - 파일 다운로드 - </Button> - )} + <div className="text-center min-w-0"> + <div className="text-sm font-medium mb-2 break-words">ISO 인증서</div> + <div className="text-sm text-muted-foreground"> + {attachmentsByType.ISO_CERTIFICATION?.length || 0}건 </div> + {attachmentsByType.ISO_CERTIFICATION?.length > 0 && ( + <Button + variant="outline" + size="sm" + className="mt-2" + onClick={() => handleAttachmentFileManagement("ISO_CERTIFICATION", "ISO 인증서")} + > + 파일 다운로드 + </Button> + )} </div> {/* 기타 첨부파일 (GENERAL) */} - <div className="flex flex-col items-center gap-3"> - <div className="text-sm font-medium text-center">기타 첨부파일</div> - <div className="text-center"> - <div className="text-lg font-semibold text-primary"> - {attachmentsByType.GENERAL?.length || 0}건 - </div> - {attachmentsByType.GENERAL?.length > 0 && ( - <Button - variant="outline" - size="sm" - className="mt-2" - onClick={() => handleAttachmentFileManagement("GENERAL", "기타 첨부파일")} - > - 파일 다운로드 - </Button> - )} + <div className="text-center min-w-0"> + <div className="text-sm font-medium mb-2 break-words">기타 첨부파일</div> + <div className="text-sm text-muted-foreground"> + {attachmentsByType.GENERAL?.length || 0}건 </div> + {attachmentsByType.GENERAL?.length > 0 && ( + <Button + variant="outline" + size="sm" + className="mt-2" + onClick={() => handleAttachmentFileManagement("GENERAL", "기타 첨부파일")} + > + 파일 다운로드 + </Button> + )} </div> </div> } @@ -1141,6 +1157,24 @@ export default function BasicInfoClient({ } }} > + 조직도 ({getPQAttachmentsByCode("1-5").length}건) + </Button> + <Button + variant="outline" + className="text-xs w-32 flex items-center gap-2" + onClick={() => { + const files = getPQAttachmentsByCode("1-10"); + if (files.length > 0) { + files.forEach((file, index) => { + setTimeout(() => { + handleAttachmentDownload(file.filePath, file.fileName); + }, index * 500); + }); + } else { + toast.info("협력업체정보 파일이 없습니다."); + } + }} + > 협력업체정보 ({getPQAttachmentsByCode("1-10").length}건) </Button> <Button @@ -1352,20 +1386,25 @@ export default function BasicInfoClient({ const metricsData = creditTransformedData?.calculatedMetrics || initialData.calculatedMetrics || {}; // 실제 데이터에서 연도 추출 (고정 연도 사용하지 않음) - const years = Object.keys(salesData).sort().reverse().slice(0, 3); + let years = Object.keys(salesData).sort().reverse().slice(0, 3); - // 데이터가 없는 경우 기본 연도 사용 - if (years.length === 0 && !getCurrentResult()?.data) { - years.push("20231231", "20221231", "20211231"); + // 데이터가 없는 경우 빈 행 3개 생성 + if (years.length === 0) { + years = ["", "", ""]; } - return years.map((dateKey) => { - const formattedDate = dateKey; // YYYYMMDD 형식으로 표시 + // 3개 미만인 경우 빈 행으로 채우기 + while (years.length < 3) { + years.push(""); + } + + return years.map((dateKey, index) => { + const formattedDate = dateKey; // YYYYMMDD 형식으로 표시 (빈 문자열일 수 있음) const yearSalesData = salesData[dateKey]; const yearMetricsData = metricsData[dateKey]; return ( - <TableRow key={dateKey}> + <TableRow key={dateKey || `empty-${index}`}> <TableCell className="text-center font-medium border-r bg-yellow-50"> {formattedDate} </TableCell> @@ -1430,7 +1469,7 @@ export default function BasicInfoClient({ })) }]); const inspectionFiles = getPQAttachmentsByCode("4-1"); - + return ( <InfoSection key={submission.submission.id} @@ -1735,14 +1774,14 @@ export default function BasicInfoClient({ </div> } /> - + </div> - <DocumentStatusDialog - open={contractDialogOpen} - onOpenChange={setContractDialogOpen} - registration={registrationData} - isVendorUser={false} - /> + <DocumentStatusDialog + open={contractDialogOpen} + onOpenChange={setContractDialogOpen} + registration={registrationData} + isVendorUser={false} + /> </div> ); } |
