summaryrefslogtreecommitdiff
path: root/lib/rfq-last
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-12 04:35:12 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-12 04:35:12 +0000
commite3eff0780834c7e0bd485d7229a6c2326f157482 (patch)
tree64a9a7c0f999fcd12f482377d0bd7f64fd88d5a2 /lib/rfq-last
parentddd644eb2c5ef47ef32e299c647e781f9f8b3a0a (diff)
(임수민) 구매 피드팩 수정 사항
Diffstat (limited to 'lib/rfq-last')
-rw-r--r--lib/rfq-last/vendor-response/editor/quotation-items-table.tsx378
-rw-r--r--lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx22
-rw-r--r--lib/rfq-last/vendor/vendor-detail-dialog.tsx184
3 files changed, 469 insertions, 115 deletions
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">