diff options
Diffstat (limited to 'lib')
4 files changed, 498 insertions, 115 deletions
diff --git a/lib/basic-contract/status-detail/basic-contracts-detail-table.tsx b/lib/basic-contract/status-detail/basic-contracts-detail-table.tsx index f18359de..80c3352a 100644 --- a/lib/basic-contract/status-detail/basic-contracts-detail-table.tsx +++ b/lib/basic-contract/status-detail/basic-contracts-detail-table.tsx @@ -115,6 +115,35 @@ export function BasicContractsDetailTable({ templateId, promises }: BasicContrac clearOnDefault: true, }) + // rowAction 처리 로직 추가 + React.useEffect(() => { + if (rowAction) { + switch (rowAction.type) { + case "view": + // GTC 템플릿인 경우 GTC 협의 페이지로 이동 + const contract = rowAction.row.original; + if (contract.templateName?.includes('GTC')) { + const contractGtcData = gtcData[contract.id]; + if (contractGtcData?.gtcDocumentId) { + const gtcUrl = `/evcp/basic-contract/vendor-gtc/${contractGtcData.gtcDocumentId}?vendorId=${contract.vendorId}&vendorName=${encodeURIComponent(contract.vendorName || '')}&contractId=${contract.id}&templateId=${contract.templateId}`; + router.push(gtcUrl); + } else { + toast.error("GTC 문서 정보를 찾을 수 없습니다."); + } + } else { + // 일반 계약서인 경우: 상세 정보를 보여주는 기능 + // 현재는 준비 중이지만, 향후 다이얼로그나 시트를 열 수 있음 + toast.info("상세 정보 기능은 준비 중입니다."); + } + setRowAction(null); + break; + default: + setRowAction(null); + break; + } + } + }, [rowAction, router, gtcData]); + return ( <DataTable table={table}> <DataTableAdvancedToolbar diff --git a/lib/rfq-last/vendor-response/editor/quotation-items-table.tsx b/lib/rfq-last/vendor-response/editor/quotation-items-table.tsx index d2e0ff0b..9f2af046 100644 --- a/lib/rfq-last/vendor-response/editor/quotation-items-table.tsx +++ b/lib/rfq-last/vendor-response/editor/quotation-items-table.tsx @@ -13,13 +13,14 @@ import { Badge } from "@/components/ui/badge" import { Label } from "@/components/ui/label" import { Separator } from "@/components/ui/separator" import { ScrollArea } from "@/components/ui/scroll-area" -import { CalendarIcon, Eye, FileText, Download, ExternalLink } from "lucide-react" +import { CalendarIcon, Eye, FileText, Download, ExternalLink, Upload, FileDown } from "lucide-react" import { format } from "date-fns" import { cn, formatCurrency } from "@/lib/utils" -import { useState, useEffect } from "react" +import { useState, useEffect, useRef } from "react" import { toast } from "sonner" import { checkPosFileExists, getDownloadUrlByMaterialCode } from "@/lib/pos" import { PosFileSelectionDialog } from "@/lib/pos/components/pos-file-selection-dialog" +import ExcelJS from "exceljs" import { Dialog, DialogContent, @@ -58,6 +59,10 @@ export default function QuotationItemsTable({ prItems }: QuotationItemsTableProp }>>([]) const [loadingPosFiles, setLoadingPosFiles] = useState(false) const [downloadingFileIndex, setDownloadingFileIndex] = useState<number | null>(null) + + // 엑셀 import/export 관련 상태 + const fileInputRef = useRef<HTMLInputElement>(null) + const [isImporting, setIsImporting] = useState(false) const currency = watch("vendorCurrency") || "USD" const quotationItems = watch("quotationItems") @@ -78,8 +83,15 @@ export default function QuotationItemsTable({ prItems }: QuotationItemsTableProp // currency는 vendorCurrency를 따름 setValue(`quotationItems.${index}.currency`, currency) + + // 납기일은 PR납기요청일을 Default로 설정 (기존 값이 없을 때만) + const currentDeliveryDate = quotationItems?.[index]?.vendorDeliveryDate + if (prItem.deliveryDate && !currentDeliveryDate) { + setValue(`quotationItems.${index}.vendorDeliveryDate`, new Date(prItem.deliveryDate)) + } }) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [prItems, setValue, currency]) // 단가 * 수량 계산 @@ -110,12 +122,12 @@ export default function QuotationItemsTable({ prItems }: QuotationItemsTableProp } } - // 납기일 초기화 - const clearAllDeliveryDates = () => { - fields.forEach((_, index) => { - setValue(`quotationItems.${index}.vendorDeliveryDate`, undefined) - }) - } + // 납기일 초기화 (주석처리) + // const clearAllDeliveryDates = () => { + // fields.forEach((_, index) => { + // setValue(`quotationItems.${index}.vendorDeliveryDate`, undefined) + // }) + // } // 사양서 링크 열기 const handleOpenSpec = (specUrl: string) => { @@ -190,6 +202,183 @@ export default function QuotationItemsTable({ prItems }: QuotationItemsTableProp setDownloadingFileIndex(null) } + // 엑셀 Export + const handleExportExcel = async () => { + try { + const workbook = new ExcelJS.Workbook() + const worksheet = workbook.addWorksheet('견적품목') + + // 헤더 설정 + worksheet.columns = [ + { header: 'No', key: 'no', width: 10 }, + { header: 'PR No', key: 'prNo', width: 15 }, + { header: 'PR 아이템', key: 'prItem', width: 15 }, + { header: '자재코드', key: 'materialCode', width: 20 }, + { header: '자재명', key: 'materialDescription', width: 40 }, + { header: '수량', key: 'quantity', width: 15 }, + { header: '단위', key: 'uom', width: 10 }, + { header: '중량', key: 'grossWeight', width: 15 }, + { header: '중량단위', key: 'gwUom', width: 15 }, + { header: '단가', key: 'unitPrice', width: 20 }, + { header: '총액', key: 'totalPrice', width: 20 }, + { header: 'Spec.', key: 'specNo', width: 20 }, + { header: 'POS', key: 'pos', width: 15 }, + { header: 'PR납기 요청일', key: 'deliveryDate', width: 20 }, + { header: '벤더 가능납기일', key: 'vendorDeliveryDate', width: 20 }, + ] + + // 데이터 추가 + fields.forEach((field, index) => { + const prItem = prItems[index] + const quotationItem = quotationItems[index] + const row = worksheet.addRow({ + no: index + 1, + prNo: prItem?.prNo || '', + prItem: prItem?.prItem || prItem?.rfqItem || '', + materialCode: prItem?.materialCode || '', + materialDescription: prItem?.materialDescription || '', + quantity: prItem?.quantity || 0, + uom: prItem?.uom || '', + grossWeight: prItem?.grossWeight || 0, + gwUom: prItem?.gwUom || '', + unitPrice: quotationItem?.unitPrice || 0, + totalPrice: quotationItem?.totalPrice || 0, + specNo: prItem?.specNo || '', + pos: prItem?.materialCode ? 'POS' : '', + deliveryDate: prItem?.deliveryDate ? format(new Date(prItem.deliveryDate), 'yyyy-MM-dd') : '', + vendorDeliveryDate: quotationItem?.vendorDeliveryDate ? format(new Date(quotationItem.vendorDeliveryDate), 'yyyy-MM-dd') : '', + }) + }) + + // 스타일 적용 + worksheet.getRow(1).font = { bold: true } + worksheet.getRow(1).fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFE0E0E0' } + } + + // 파일 다운로드 + const buffer = await workbook.xlsx.writeBuffer() + const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }) + const url = window.URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + link.download = `견적품목_${format(new Date(), 'yyyyMMdd_HHmmss')}.xlsx` + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + window.URL.revokeObjectURL(url) + + toast.success('엑셀 파일이 다운로드되었습니다.') + } catch (error) { + console.error('엑셀 Export 오류:', error) + toast.error('엑셀 파일 다운로드에 실패했습니다.') + } + } + + // 엑셀 Import + const handleImportExcel = async (event: React.ChangeEvent<HTMLInputElement>) => { + const file = event.target.files?.[0] + if (!file) return + + if (!file.name.endsWith('.xlsx') && !file.name.endsWith('.xls')) { + toast.error('Excel 파일(.xlsx 또는 .xls)만 업로드 가능합니다') + return + } + + setIsImporting(true) + try { + const workbook = new ExcelJS.Workbook() + const arrayBuffer = await file.arrayBuffer() + await workbook.xlsx.load(arrayBuffer) + + const worksheet = workbook.worksheets[0] + if (!worksheet) { + toast.error('워크시트를 찾을 수 없습니다.') + return + } + + // 헤더 매핑 + const headerRow = worksheet.getRow(1) + const headerMap: { [key: string]: number } = {} + headerRow.eachCell((cell, colNumber) => { + const header = String(cell.value || '').trim() + if (header) { + headerMap[header] = colNumber + } + }) + + // 데이터 읽기 + let successCount = 0 + let errorCount = 0 + + worksheet.eachRow((row, rowNumber) => { + if (rowNumber === 1) return // 헤더 건너뛰기 + + const index = rowNumber - 2 // 0-based index + if (index >= fields.length) return + + try { + const unitPriceCol = headerMap['단가'] || headerMap['unitPrice'] + const totalPriceCol = headerMap['총액'] || headerMap['totalPrice'] + const vendorDeliveryDateCol = headerMap['벤더 가능납기일'] || headerMap['vendorDeliveryDate'] + + if (unitPriceCol) { + const unitPriceValue = row.getCell(unitPriceCol).value + const unitPrice = typeof unitPriceValue === 'number' ? unitPriceValue : parseFloat(String(unitPriceValue || 0)) + if (!isNaN(unitPrice) && unitPrice >= 0) { + setValue(`quotationItems.${index}.unitPrice`, Math.floor(unitPrice)) + calculateTotal(index) + successCount++ + } + } + + if (totalPriceCol) { + const totalPriceValue = row.getCell(totalPriceCol).value + const totalPrice = typeof totalPriceValue === 'number' ? totalPriceValue : parseFloat(String(totalPriceValue || 0)) + if (!isNaN(totalPrice) && totalPrice >= 0) { + setValue(`quotationItems.${index}.totalPrice`, totalPrice) + } + } + + if (vendorDeliveryDateCol) { + const dateValue = row.getCell(vendorDeliveryDateCol).value + if (dateValue) { + let date: Date | null = null + if (dateValue instanceof Date) { + date = dateValue + } else if (typeof dateValue === 'string') { + date = new Date(dateValue) + } else if (typeof dateValue === 'number') { + // Excel serial date + date = new Date((dateValue - 25569) * 86400 * 1000) + } + + if (date && !isNaN(date.getTime())) { + setValue(`quotationItems.${index}.vendorDeliveryDate`, date) + } + } + } + } catch (error) { + console.error(`Row ${rowNumber} import error:`, error) + errorCount++ + } + }) + + if (fileInputRef.current) { + fileInputRef.current.value = '' + } + + toast.success(`${successCount}개 항목이 성공적으로 가져왔습니다.${errorCount > 0 ? ` (${errorCount}개 오류)` : ''}`) + } catch (error) { + console.error('엑셀 Import 오류:', error) + toast.error('엑셀 파일 가져오기에 실패했습니다.') + } finally { + setIsImporting(false) + } + } + const totalAmount = quotationItems?.reduce( (sum: number, item: any) => sum + (item.totalPrice || 0), 0 ) || 0 @@ -448,14 +637,41 @@ export default function QuotationItemsTable({ prItems }: QuotationItemsTableProp <CalendarIcon className="h-4 w-4 mr-1" /> 전체 납기일 설정 </Button> - <Button + {/* 납기일 초기화 버튼 주석처리 */} + {/* <Button type="button" variant="outline" size="sm" onClick={clearAllDeliveryDates} > 납기일 초기화 + </Button> */} + <Button + type="button" + variant="outline" + size="sm" + onClick={handleExportExcel} + > + <FileDown className="h-4 w-4 mr-1" /> + 엑셀 Export + </Button> + <Button + type="button" + variant="outline" + size="sm" + onClick={() => fileInputRef.current?.click()} + disabled={isImporting} + > + <Upload className="h-4 w-4 mr-1" /> + {isImporting ? '가져오는 중...' : '엑셀 Import'} </Button> + <input + ref={fileInputRef} + type="file" + accept=".xlsx,.xls" + onChange={handleImportExcel} + style={{ display: 'none' }} + /> </div> <div className="text-right"> <p className="text-sm text-muted-foreground">총 견적금액</p> @@ -483,9 +699,10 @@ export default function QuotationItemsTable({ prItems }: QuotationItemsTableProp <TableHead className="w-[60px]">중량단위</TableHead> <TableHead className="w-[150px]">단가</TableHead> <TableHead className="text-right w-[150px]">총액</TableHead> + <TableHead className="w-[120px]">Spec.</TableHead> + <TableHead className="w-[100px]">POS</TableHead> <TableHead className="w-[150px]">PR납기 요청일</TableHead> - <TableHead className="w-[180px]">사양/POS</TableHead> - <TableHead className="w-[120px]">프로젝트</TableHead> + <TableHead className="w-[150px]">벤더 가능납기일</TableHead> <TableHead className="w-[80px]">상세</TableHead> </TableRow> </TableHeader> @@ -584,6 +801,58 @@ export default function QuotationItemsTable({ prItems }: QuotationItemsTableProp {formatCurrency(quotationItem?.totalPrice || 0, currency)} </TableCell> <TableCell> + {/* Spec. 칼럼 */} + <div className="flex flex-col gap-1"> + {prItem?.specNo && ( + <div className="flex items-center gap-1"> + <span className="text-xs font-mono">{prItem.specNo}</span> + {prItem.specUrl && ( + <Button + type="button" + variant="ghost" + size="sm" + className="h-5 w-5 p-0" + onClick={() => handleOpenSpec(prItem.specUrl!)} + title="사양서 열기" + > + <ExternalLink className="h-3 w-3" /> + </Button> + )} + </div> + )} + {!prItem?.specNo && <span className="text-xs text-muted-foreground">-</span>} + </div> + </TableCell> + <TableCell> + {/* POS 칼럼 */} + {prItem?.materialCode ? ( + <div className="flex items-center gap-1"> + <FileText className="h-3 w-3 text-green-500" /> + <Button + type="button" + variant="ghost" + size="sm" + className="h-5 p-1 text-xs text-green-600 hover:text-green-800" + onClick={() => handleOpenPosDialog(prItem.materialCode!)} + disabled={loadingPosFiles && selectedMaterialCode === prItem.materialCode} + title={`POS 파일 다운로드 (자재코드: ${prItem.materialCode})`} + > + <Download className="h-3 w-3 mr-1" /> + {loadingPosFiles && selectedMaterialCode === prItem.materialCode ? '조회중...' : 'POS'} + </Button> + </div> + ) : ( + <span className="text-xs text-muted-foreground">-</span> + )} + </TableCell> + <TableCell> + {/* PR납기 요청일 */} + <span className="text-sm"> + {prItem?.deliveryDate ? format(new Date(prItem.deliveryDate), "yyyy-MM-dd") : "-"} + </span> + </TableCell> + <TableCell> + {/* 벤더 가능납기일 */} <Popover> <PopoverTrigger asChild> <Button @@ -597,13 +866,15 @@ export default function QuotationItemsTable({ prItems }: QuotationItemsTableProp <CalendarIcon className="mr-2 h-3 w-3" /> {quotationItem?.vendorDeliveryDate ? format(quotationItem.vendorDeliveryDate, "yyyy-MM-dd") + : prItem?.deliveryDate + ? format(new Date(prItem.deliveryDate), "yyyy-MM-dd") : "선택"} </Button> </PopoverTrigger> <PopoverContent className="w-auto p-0" align="start"> <Calendar mode="single" - selected={quotationItem?.vendorDeliveryDate} + selected={quotationItem?.vendorDeliveryDate || (prItem?.deliveryDate ? new Date(prItem.deliveryDate) : undefined)} onSelect={(date) => setValue(`quotationItems.${index}.vendorDeliveryDate`, date)} initialFocus /> @@ -619,67 +890,6 @@ export default function QuotationItemsTable({ prItems }: QuotationItemsTableProp )} </TableCell> <TableCell> - <div className="flex flex-col gap-1"> - {/* 사양서 정보 */} - {(prItem?.specNo || prItem?.specUrl) && ( - <div className="flex items-center gap-1"> - {prItem.specNo && ( - <span className="text-xs font-mono">{prItem.specNo}</span> - )} - {prItem.specUrl && ( - <Button - type="button" - variant="ghost" - size="sm" - className="h-5 w-5 p-0" - onClick={() => handleOpenSpec(prItem.specUrl!)} - title="사양서 열기" - > - <ExternalLink className="h-3 w-3" /> - </Button> - )} - </div> - )} - - {/* POS 파일 다운로드 */} - {prItem?.materialCode && ( - <div className="flex items-center gap-1"> - <FileText className="h-3 w-3 text-green-500" /> - <Button - type="button" - variant="ghost" - size="sm" - className="h-5 p-1 text-xs text-green-600 hover:text-green-800" - onClick={() => handleOpenPosDialog(prItem.materialCode!)} - disabled={loadingPosFiles && selectedMaterialCode === prItem.materialCode} - title={`POS 파일 다운로드 (자재코드: ${prItem.materialCode})`} - > - <Download className="h-3 w-3 mr-1" /> - {loadingPosFiles && selectedMaterialCode === prItem.materialCode ? '조회중...' : 'POS'} - </Button> - </div> - )} - - {/* 트래킹 번호 */} - {prItem?.trackingNo && ( - <div className="text-xs text-muted-foreground"> - TRK: {prItem.trackingNo} - </div> - )} - </div> - </TableCell> - <TableCell> - <div className="text-xs"> - {[ - prItem?.projectDef && `${prItem.projectDef}`, - prItem?.projectSc && `SC: ${prItem.projectSc}`, - prItem?.projectKl && `KL: ${prItem.projectKl}`, - prItem?.projectLc && `LC: ${prItem.projectLc}`, - prItem?.projectDl && `DL: ${prItem.projectDl}` - ].filter(Boolean).join(" | ") || "-"} - </div> - </TableCell> - <TableCell> <Button type="button" variant="ghost" @@ -704,23 +914,11 @@ export default function QuotationItemsTable({ prItems }: QuotationItemsTableProp <div className="mt-4 flex justify-end"> <Card className="w-[400px]"> <CardContent className="pt-4"> - <div className="space-y-2"> - <div className="flex justify-between text-sm"> - <span className="text-muted-foreground">소계</span> - <span>{formatCurrency(totalAmount, currency)}</span> - </div> - <div className="flex justify-between text-sm"> - <span className="text-muted-foreground">통화</span> - <span>{currency}</span> - </div> - <div className="border-t pt-2"> - <div className="flex justify-between"> - <span className="font-semibold">총 견적금액</span> - <span className="text-xl font-bold text-primary"> - {formatCurrency(totalAmount, currency)} - </span> - </div> - </div> + <div className="flex justify-between"> + <span className="font-semibold">총 견적금액</span> + <span className="text-xl font-bold text-primary"> + {formatCurrency(totalAmount, currency)} + </span> </div> </CardContent> </Card> diff --git a/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx b/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx index c983dd55..4c14a7f3 100644 --- a/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx +++ b/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx @@ -285,6 +285,21 @@ export default function VendorResponseEditor({ } // 제출일 경우에만 validation 수행 + // 0원 입력 확인 + const items = methods.watch('quotationItems') || [] + const zeroPriceItems = items.filter((item: any) => item.unitPrice === 0 || item.totalPrice === 0) + + if (zeroPriceItems.length > 0) { + const confirmed = window.confirm( + `견적품목 중 ${zeroPriceItems.length}개 항목에 0원이 입력되어 있습니다.\n` + + `계속 제출하시겠습니까?` + ) + if (!confirmed) { + setActiveTab('items') + return + } + } + methods.handleSubmit( (data) => onSubmit(data, isSubmit), (errors) => { @@ -655,13 +670,18 @@ export default function VendorResponseEditor({ type="button" variant="default" onClick={() => handleFormSubmit(true)} // 직접 핸들러 호출 - disabled={loading || !allContractsSigned || isSubmitted} + disabled={loading || !allContractsSigned || isSubmitted || activeTab !== 'attachments'} > {!allContractsSigned ? ( <> <AlertCircle className="h-4 w-4 mr-2" /> 기본계약 서명 필요 </> + ) : activeTab !== 'attachments' ? ( + <> + <AlertCircle className="h-4 w-4 mr-2" /> + 첨부파일 화면에서 제출 가능 + </> ) : loading ? ( <> <div className="h-4 w-4 mr-2 animate-spin rounded-full border-2 border-current border-t-transparent" /> diff --git a/lib/rfq-last/vendor/vendor-detail-dialog.tsx b/lib/rfq-last/vendor/vendor-detail-dialog.tsx index f379b032..181e33c8 100644 --- a/lib/rfq-last/vendor/vendor-detail-dialog.tsx +++ b/lib/rfq-last/vendor/vendor-detail-dialog.tsx @@ -463,36 +463,172 @@ export function VendorResponseDetailDialog({ <CardContent> <div className="grid grid-cols-2 gap-4"> <div className="space-y-3"> - <div className="flex items-center justify-between"> - <span className="text-sm text-muted-foreground">제안 통화</span> - <Badge variant={response?.pricing?.vendorCurrency === data.currency ? "outline" : "default"}> - {response?.pricing?.vendorCurrency || data.currency} - </Badge> + <div className="space-y-1"> + <div className="flex items-center justify-between"> + <span className="text-sm text-muted-foreground">제안 통화</span> + <div className="flex items-center gap-2"> + <Badge variant={response?.pricing?.vendorCurrency === data.currency ? "outline" : "default"}> + {response?.pricing?.vendorCurrency || data.currency} + </Badge> + {response?.pricing?.vendorCurrency && response.pricing.vendorCurrency !== data.currency && ( + <Badge variant="outline" className="text-xs">변경</Badge> + )} + </div> + </div> + {response?.pricing?.vendorCurrency && + response.pricing.vendorCurrency !== data.currency && + response?.changeReasons?.currency && ( + <p className="text-xs text-muted-foreground text-right pl-4"> + 사유: {response.changeReasons.currency} + </p> + )} </div> - <div className="flex items-center justify-between"> - <span className="text-sm text-muted-foreground">제안 지급조건</span> - <span className="font-medium"> - {response?.vendorTerms?.paymentTermsCode || data.paymentTermsCode} - </span> + <div className="space-y-1"> + <div className="flex items-center justify-between"> + <span className="text-sm text-muted-foreground">제안 지급조건</span> + <div className="flex items-center gap-2"> + <span className="font-medium"> + {response?.vendorTerms?.paymentTermsCode || data.paymentTermsCode} + </span> + {response?.vendorTerms?.paymentTermsCode && + response.vendorTerms.paymentTermsCode !== data.paymentTermsCode && ( + <Badge variant="outline" className="text-xs">변경</Badge> + )} + </div> + </div> + {response?.vendorTerms?.paymentTermsCode && + response.vendorTerms.paymentTermsCode !== data.paymentTermsCode && + response?.changeReasons?.paymentTerms && ( + <p className="text-xs text-muted-foreground text-right pl-4"> + 사유: {response.changeReasons.paymentTerms} + </p> + )} </div> - <div className="flex items-center justify-between"> - <span className="text-sm text-muted-foreground">제안 인코텀즈</span> - <span className="font-medium"> - {response?.vendorTerms?.incotermsCode || data.incotermsCode} - </span> + <div className="space-y-1"> + <div className="flex items-center justify-between"> + <span className="text-sm text-muted-foreground">제안 인코텀즈</span> + <div className="flex items-center gap-2"> + <span className="font-medium"> + {response?.vendorTerms?.incotermsCode || data.incotermsCode} + </span> + {response?.vendorTerms?.incotermsCode && + response.vendorTerms.incotermsCode !== data.incotermsCode && ( + <Badge variant="outline" className="text-xs">변경</Badge> + )} + </div> + </div> + {response?.vendorTerms?.incotermsCode && + response.vendorTerms.incotermsCode !== data.incotermsCode && + response?.changeReasons?.incoterms && ( + <p className="text-xs text-muted-foreground text-right pl-4"> + 사유: {response.changeReasons.incoterms} + </p> + )} </div> + {response?.vendorTerms?.taxCode && ( + <div className="space-y-1"> + <div className="flex items-center justify-between"> + <span className="text-sm text-muted-foreground">제안 세금조건</span> + <div className="flex items-center gap-2"> + <span className="font-medium">{response.vendorTerms.taxCode}</span> + {response.vendorTerms.taxCode !== data.taxCode && ( + <Badge variant="outline" className="text-xs">변경</Badge> + )} + </div> + </div> + {response.vendorTerms.taxCode !== data.taxCode && + response?.changeReasons?.tax && ( + <p className="text-xs text-muted-foreground text-right pl-4"> + 사유: {response.changeReasons.tax} + </p> + )} + </div> + )} </div> <div className="space-y-3"> - <div className="flex items-center justify-between"> - <span className="text-sm text-muted-foreground">제안 납기일</span> - <span className="text-sm"> - {response?.vendorTerms?.deliveryDate - ? format(new Date(response.vendorTerms.deliveryDate), "yyyy-MM-dd") - : data.deliveryDate - ? format(new Date(data.deliveryDate), "yyyy-MM-dd") - : "-"} - </span> + <div className="space-y-1"> + <div className="flex items-center justify-between"> + <span className="text-sm text-muted-foreground">제안 납기일</span> + <div className="flex items-center gap-2"> + <span className="text-sm"> + {response?.vendorTerms?.deliveryDate + ? format(new Date(response.vendorTerms.deliveryDate), "yyyy-MM-dd") + : data.deliveryDate + ? format(new Date(data.deliveryDate), "yyyy-MM-dd") + : "-"} + </span> + {response?.vendorTerms?.deliveryDate && data.deliveryDate && + new Date(response.vendorTerms.deliveryDate).getTime() !== new Date(data.deliveryDate).getTime() && ( + <Badge variant="outline" className="text-xs">변경</Badge> + )} + </div> + </div> + {response?.vendorTerms?.deliveryDate && data.deliveryDate && + new Date(response.vendorTerms.deliveryDate).getTime() !== new Date(data.deliveryDate).getTime() && + response?.changeReasons?.deliveryDate && ( + <p className="text-xs text-muted-foreground text-right pl-4"> + 사유: {response.changeReasons.deliveryDate} + </p> + )} </div> + {response?.vendorTerms?.placeOfShipping && ( + <div className="space-y-1"> + <div className="flex items-center justify-between"> + <span className="text-sm text-muted-foreground">제안 선적지</span> + <div className="flex items-center gap-2"> + <span className="text-sm">{response.vendorTerms.placeOfShipping}</span> + {response.vendorTerms.placeOfShipping !== data.placeOfShipping && ( + <Badge variant="outline" className="text-xs">변경</Badge> + )} + </div> + </div> + {response.vendorTerms.placeOfShipping !== data.placeOfShipping && + response?.changeReasons?.shipping && ( + <p className="text-xs text-muted-foreground text-right pl-4"> + 사유: {response.changeReasons.shipping} + </p> + )} + </div> + )} + {response?.vendorTerms?.placeOfDestination && ( + <div className="space-y-1"> + <div className="flex items-center justify-between"> + <span className="text-sm text-muted-foreground">제안 하역지</span> + <div className="flex items-center gap-2"> + <span className="text-sm">{response.vendorTerms.placeOfDestination}</span> + {response.vendorTerms.placeOfDestination !== data.placeOfDestination && ( + <Badge variant="outline" className="text-xs">변경</Badge> + )} + </div> + </div> + {response.vendorTerms.placeOfDestination !== data.placeOfDestination && + response?.changeReasons?.shipping && ( + <p className="text-xs text-muted-foreground text-right pl-4"> + 사유: {response.changeReasons.shipping} + </p> + )} + </div> + )} + {response?.vendorTerms?.contractDuration && ( + <div className="space-y-1"> + <div className="flex items-center justify-between"> + <span className="text-sm text-muted-foreground">제안 계약기간</span> + <div className="flex items-center gap-2"> + <span className="text-sm">{response.vendorTerms.contractDuration}</span> + {response.vendorTerms.contractDuration !== data.contractDuration && ( + <Badge variant="outline" className="text-xs">변경</Badge> + )} + </div> + </div> + {response.vendorTerms.contractDuration !== data.contractDuration && ( + <p className="text-xs text-muted-foreground text-right pl-4"> + {response?.changeReasons?.contractDuration + ? `사유: ${response.changeReasons.contractDuration}` + : "변경 사유 없음"} + </p> + )} + </div> + )} <div className="flex items-center justify-between"> <span className="text-sm text-muted-foreground">총 견적금액</span> <span className="font-bold text-lg"> |
