summaryrefslogtreecommitdiff
path: root/lib/rfq-last/quotation-compare-view.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-10-17 08:08:33 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-10-17 08:08:33 +0000
commit1540eac291761ffd8fc1947ed626e4e4a4407922 (patch)
treea7b6ae8060e164f249651cf6ef8b0c2e868019e9 /lib/rfq-last/quotation-compare-view.tsx
parent55b6153dfce83a1cf2be72cbc3413d78084e8da1 (diff)
(최겸) 견적입찰 비교관련 수정
Diffstat (limited to 'lib/rfq-last/quotation-compare-view.tsx')
-rw-r--r--lib/rfq-last/quotation-compare-view.tsx466
1 files changed, 306 insertions, 160 deletions
diff --git a/lib/rfq-last/quotation-compare-view.tsx b/lib/rfq-last/quotation-compare-view.tsx
index 723d1044..527fc4d8 100644
--- a/lib/rfq-last/quotation-compare-view.tsx
+++ b/lib/rfq-last/quotation-compare-view.tsx
@@ -28,6 +28,8 @@ import {
X,
RefreshCw,
Clock,
+ Download,
+ Paperclip,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
@@ -193,13 +195,14 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
switch (selectedContractType) {
case "PO":
+ const poSelectionReason: string | undefined = selectedVendor.selectionReason || undefined;
result = await createPO({
rfqId: data.rfqInfo.id,
vendorId: selectedVendor.vendorId,
vendorName: selectedVendor.vendorName,
totalAmount: selectedVendor.totalAmount,
currency: selectedVendor.currency,
- selectionReason: selectedVendor.selectionReason,
+ selectionReason: poSelectionReason,
});
break;
@@ -712,18 +715,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
<table className="w-full">
<thead>
<tr className="border-b">
- <th className="text-left p-3 font-semibold">항목</th>
- <th className="text-left p-3 font-semibold">구매자 제시</th>
+ <th className="text-left p-3 font-semibold" rowSpan={2}>항목</th>
+ </tr>
+ <tr className="border-b bg-gray-50">
{data.vendors.map((vendor) => (
- <th key={vendor.vendorId} className={cn(
- "text-left p-3 font-semibold",
- vendor.isSelected && "bg-blue-50"
- )}>
- {vendor.vendorName}
+ <React.Fragment key={vendor.vendorId}>
+ <th className={cn(
+ "text-center p-2 text-xs font-medium border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ SHI 제시
+ </th>
+ <th className={cn(
+ "text-center p-2 text-xs font-medium",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="font-bold">
+ {vendor.vendorName}
+ </div>
{vendor.isSelected && (
<Badge className="ml-2 bg-blue-600 text-xs">선정</Badge>
)}
- </th>
+ </th>
+ </React.Fragment>
))}
</tr>
</thead>
@@ -731,21 +745,28 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 통화 */}
<tr>
<td className="p-3 font-medium">통화</td>
- <td className="p-3">{data.vendors[0]?.buyerConditions.currency}</td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0]; // 최신 응답 (이미 정렬되어 있음)
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
- {latestResponse?.vendorConditions?.currency || vendor.buyerConditions.currency}
- {latestResponse?.vendorConditions?.currency && latestResponse.vendorConditions.currency !== vendor.buyerConditions.currency && (
- <Badge variant="destructive" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ {vendor.buyerConditions.currency}
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ {latestResponse?.vendorConditions?.currency || vendor.buyerConditions.currency}
+ {latestResponse?.vendorConditions?.currency && latestResponse.vendorConditions.currency !== vendor.buyerConditions.currency && (
+ <Badge variant="destructive" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -753,42 +774,47 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 지급조건 */}
<tr>
<td className="p-3 font-medium">지급조건</td>
- <td className="p-3">
- <TooltipProvider>
- <Tooltip>
- <TooltipTrigger className="cursor-help border-b border-dashed">
- {data.vendors[0]?.buyerConditions.paymentTermsCode}
- </TooltipTrigger>
- <TooltipContent>
- {data.vendors[0]?.buyerConditions.paymentTermsDesc}
- </TooltipContent>
- </Tooltip>
- </TooltipProvider>
- </td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="cursor-help border-b border-dashed">
- {latestResponse?.vendorConditions?.paymentTermsCode || vendor.buyerConditions.paymentTermsCode}
+ {vendor.buyerConditions.paymentTermsCode}
</TooltipTrigger>
<TooltipContent>
- {latestResponse?.vendorConditions?.paymentTermsDesc || vendor.buyerConditions.paymentTermsDesc}
+ {vendor.buyerConditions.paymentTermsDesc}
</TooltipContent>
</Tooltip>
</TooltipProvider>
- {latestResponse?.vendorConditions?.paymentTermsCode &&
- latestResponse.vendorConditions.paymentTermsCode !== vendor.buyerConditions.paymentTermsCode && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger className="cursor-help border-b border-dashed">
+ {latestResponse?.vendorConditions?.paymentTermsCode || vendor.buyerConditions.paymentTermsCode}
+ </TooltipTrigger>
+ <TooltipContent>
+ {latestResponse?.vendorConditions?.paymentTermsDesc || vendor.buyerConditions.paymentTermsDesc}
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ {latestResponse?.vendorConditions?.paymentTermsCode &&
+ latestResponse.vendorConditions.paymentTermsCode !== vendor.buyerConditions.paymentTermsCode && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -796,42 +822,47 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 인코텀즈 */}
<tr>
<td className="p-3 font-medium">인코텀즈</td>
- <td className="p-3">
- <TooltipProvider>
- <Tooltip>
- <TooltipTrigger className="cursor-help border-b border-dashed">
- {data.vendors[0]?.buyerConditions.incotermsCode}
- </TooltipTrigger>
- <TooltipContent>
- {data.vendors[0]?.buyerConditions.incotermsDesc}
- </TooltipContent>
- </Tooltip>
- </TooltipProvider>
- </td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="cursor-help border-b border-dashed">
- {latestResponse?.vendorConditions?.incotermsCode || vendor.buyerConditions.incotermsCode}
+ {vendor.buyerConditions.incotermsCode}
</TooltipTrigger>
<TooltipContent>
- {latestResponse?.vendorConditions?.incotermsDesc || vendor.buyerConditions.incotermsDesc}
+ {vendor.buyerConditions.incotermsDesc}
</TooltipContent>
</Tooltip>
</TooltipProvider>
- {latestResponse?.vendorConditions?.incotermsCode &&
- latestResponse.vendorConditions.incotermsCode !== vendor.buyerConditions.incotermsCode && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger className="cursor-help border-b border-dashed">
+ {latestResponse?.vendorConditions?.incotermsCode || vendor.buyerConditions.incotermsCode}
+ </TooltipTrigger>
+ <TooltipContent>
+ {latestResponse?.vendorConditions?.incotermsDesc || vendor.buyerConditions.incotermsDesc}
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ {latestResponse?.vendorConditions?.incotermsCode &&
+ latestResponse.vendorConditions.incotermsCode !== vendor.buyerConditions.incotermsCode && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -839,22 +870,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 선적지 */}
<tr>
<td className="p-3 font-medium">선적지</td>
- <td className="p-3">{data.vendors[0]?.buyerConditions.placeOfShipping || "-"}</td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
- {latestResponse?.vendorConditions?.placeOfShipping || vendor.buyerConditions.placeOfShipping || "-"}
- {latestResponse?.vendorConditions?.placeOfShipping &&
- latestResponse.vendorConditions.placeOfShipping !== vendor.buyerConditions.placeOfShipping && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ {vendor.buyerConditions.placeOfShipping || "-"}
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ {latestResponse?.vendorConditions?.placeOfShipping || vendor.buyerConditions.placeOfShipping || "-"}
+ {latestResponse?.vendorConditions?.placeOfShipping &&
+ latestResponse.vendorConditions.placeOfShipping !== vendor.buyerConditions.placeOfShipping && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -862,22 +900,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 하역지 */}
<tr>
<td className="p-3 font-medium">하역지</td>
- <td className="p-3">{data.vendors[0]?.buyerConditions.placeOfDestination || "-"}</td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
- {latestResponse?.vendorConditions?.placeOfDestination || vendor.buyerConditions.placeOfDestination || "-"}
- {latestResponse?.vendorConditions?.placeOfDestination &&
- latestResponse.vendorConditions.placeOfDestination !== vendor.buyerConditions.placeOfDestination && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ {vendor.buyerConditions.placeOfDestination || "-"}
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ {latestResponse?.vendorConditions?.placeOfDestination || vendor.buyerConditions.placeOfDestination || "-"}
+ {latestResponse?.vendorConditions?.placeOfDestination &&
+ latestResponse.vendorConditions.placeOfDestination !== vendor.buyerConditions.placeOfDestination && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -885,30 +930,35 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 납기일 */}
<tr>
<td className="p-3 font-medium">납기일</td>
- <td className="p-3">
- {data.vendors[0]?.buyerConditions.deliveryDate
- ? format(new Date(data.vendors[0].buyerConditions.deliveryDate), "yyyy-MM-dd")
- : "-"}
- </td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
- {latestResponse?.vendorConditions?.deliveryDate
- ? format(new Date(latestResponse.vendorConditions.deliveryDate), "yyyy-MM-dd")
- : vendor.buyerConditions.deliveryDate
- ? format(new Date(vendor.buyerConditions.deliveryDate), "yyyy-MM-dd")
- : "-"}
- {latestResponse?.vendorConditions?.deliveryDate && vendor.buyerConditions.deliveryDate &&
- new Date(latestResponse.vendorConditions.deliveryDate).getTime() !== new Date(vendor.buyerConditions.deliveryDate).getTime() && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ {vendor.buyerConditions.deliveryDate
+ ? format(new Date(vendor.buyerConditions.deliveryDate), "yyyy-MM-dd")
+ : "-"}
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ {latestResponse?.vendorConditions?.deliveryDate
+ ? format(new Date(latestResponse.vendorConditions.deliveryDate), "yyyy-MM-dd")
+ : vendor.buyerConditions.deliveryDate
+ ? format(new Date(vendor.buyerConditions.deliveryDate), "yyyy-MM-dd")
+ : "-"}
+ {/* {latestResponse?.vendorConditions?.deliveryDate && vendor.buyerConditions.deliveryDate &&
+ new Date(latestResponse.vendorConditions.deliveryDate).getTime() !== new Date(vendor.buyerConditions.deliveryDate).getTime() && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )} */}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -916,22 +966,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 세금조건 */}
<tr>
<td className="p-3 font-medium">세금조건</td>
- <td className="p-3">{data.vendors[0]?.buyerConditions.taxCode || "-"}</td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
- {latestResponse?.vendorConditions?.taxCode || vendor.buyerConditions.taxCode || "-"}
- {latestResponse?.vendorConditions?.taxCode &&
- latestResponse.vendorConditions.taxCode !== vendor.buyerConditions.taxCode && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ {vendor.buyerConditions.taxCode || "-"}
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ {latestResponse?.vendorConditions?.taxCode || vendor.buyerConditions.taxCode || "-"}
+ {latestResponse?.vendorConditions?.taxCode &&
+ latestResponse.vendorConditions.taxCode !== vendor.buyerConditions.taxCode && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -939,22 +996,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 계약기간 */}
<tr>
<td className="p-3 font-medium">계약기간</td>
- <td className="p-3">{data.vendors[0]?.buyerConditions.contractDuration || "-"}</td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
- {latestResponse?.vendorConditions?.contractDuration || vendor.buyerConditions.contractDuration || "-"}
- {latestResponse?.vendorConditions?.contractDuration &&
- latestResponse.vendorConditions.contractDuration !== vendor.buyerConditions.contractDuration && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ {vendor.buyerConditions.contractDuration || "-"}
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ {latestResponse?.vendorConditions?.contractDuration || vendor.buyerConditions.contractDuration || "-"}
+ {latestResponse?.vendorConditions?.contractDuration &&
+ latestResponse.vendorConditions.contractDuration !== vendor.buyerConditions.contractDuration && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -980,11 +1044,13 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
<tr className="border-b bg-gray-50">
<th className="text-left p-3 font-semibold">품목코드</th>
<th className="text-left p-3 font-semibold">품목명</th>
+ <th className="text-left p-3 font-semibold">자재분류</th>
<th className="text-right p-3 font-semibold">수량</th>
+ <th className="text-left p-3 font-semibold">규격</th>
+ <th className="text-right p-3 font-semibold">중량</th>
<th className="text-right p-3 font-semibold">단가</th>
<th className="text-right p-3 font-semibold">총액</th>
- <th className="text-left p-3 font-semibold">납기</th>
- <th className="text-left p-3 font-semibold">제조사</th>
+ <th className="text-left p-3 font-semibold">납기일자</th>
</tr>
</thead>
<tbody className="divide-y">
@@ -994,15 +1060,30 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
return (
<tr key={quoteItem.prItemId} className="hover:bg-gray-50">
- <td className="p-3 text-sm">{prItem.materialCode}</td>
- <td className="p-3">
- <p className="font-medium">{prItem.materialDescription}</p>
+ <td className="p-3 text-sm">
+ <p className="font-medium">{prItem.materialCode}</p>
<p className="text-xs text-muted-foreground">
{prItem.prNo} • {prItem.prItem}
</p>
</td>
+ <td className="p-3">
+ <p className="font-medium">{prItem.materialDescription}</p>
+ {prItem.remark && (
+ <p className="text-xs text-muted-foreground mt-1">{prItem.remark}</p>
+ )}
+ </td>
+ <td className="p-3 text-sm">
+ {prItem.materialCategory || "-"}
+ </td>
<td className="p-3 text-right">
- {quoteItem.quantity} {prItem.uom}
+ <p>{quoteItem.quantity} {prItem.uom}</p>
+ <p className="text-xs text-muted-foreground">요청: {prItem.requestedQuantity}</p>
+ </td>
+ <td className="p-3 text-sm">
+ {prItem.size || "-"}
+ </td>
+ <td className="p-3 text-right text-sm">
+ {prItem.grossWeight ? `${prItem.grossWeight} ${prItem.gwUom || ""}` : "-"}
</td>
<td className="p-3 text-right font-medium">
{formatAmount(quoteItem.unitPrice, quoteItem.currency)}
@@ -1016,16 +1097,11 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
: quoteItem.leadTime
? `${quoteItem.leadTime}일`
: "-"}
- </td>
- <td className="p-3 text-sm">
- {quoteItem.manufacturer ? (
- <div>
- <p>{quoteItem.manufacturer}</p>
- {quoteItem.modelNo && (
- <p className="text-xs text-muted-foreground">{quoteItem.modelNo}</p>
- )}
- </div>
- ) : "-"}
+ {prItem.requestedDeliveryDate && (
+ <p className="text-xs text-muted-foreground">
+ 요청: {format(new Date(prItem.requestedDeliveryDate), "yyyy-MM-dd")}
+ </p>
+ )}
</td>
</tr>
);
@@ -1054,6 +1130,76 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
</div>
</div>
)}
+
+ {/* 첨부파일 */}
+ {selectedResponse?.attachments && selectedResponse.attachments.length > 0 && (
+ <div className="mt-6 pt-6 border-t">
+ <h4 className="font-semibold mb-3 flex items-center gap-2">
+ <Paperclip className="h-4 w-4" />
+ 제출 문서 ({selectedResponse.attachments.length}건)
+ </h4>
+ <div className="space-y-2">
+ {selectedResponse.attachments.map((attachment) => (
+ <div
+ key={attachment.id}
+ className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50"
+ >
+ <div className="flex items-center gap-3 flex-1">
+ <FileText className="h-5 w-5 text-blue-500" />
+ <div className="flex-1 min-w-0">
+ <p className="font-medium text-sm truncate">
+ {attachment.originalFileName}
+ </p>
+ <div className="flex items-center gap-3 text-xs text-muted-foreground mt-1">
+ <span className="px-2 py-0.5 bg-blue-100 text-blue-700 rounded">
+ {attachment.attachmentType}
+ </span>
+ {attachment.documentNo && (
+ <span>문서번호: {attachment.documentNo}</span>
+ )}
+ {attachment.fileSize && (
+ <span>{(attachment.fileSize / 1024).toFixed(1)} KB</span>
+ )}
+ {attachment.uploadedAt && (
+ <span>
+ 업로드: {format(new Date(attachment.uploadedAt), "yyyy-MM-dd HH:mm")}
+ </span>
+ )}
+ </div>
+ {attachment.description && (
+ <p className="text-xs text-muted-foreground mt-1">
+ {attachment.description}
+ </p>
+ )}
+ {(attachment.validFrom || attachment.validTo) && (
+ <p className="text-xs text-orange-600 mt-1">
+ 유효기간: {attachment.validFrom ? format(new Date(attachment.validFrom), "yyyy-MM-dd") : "-"} ~ {attachment.validTo ? format(new Date(attachment.validTo), "yyyy-MM-dd") : "-"}
+ </p>
+ )}
+ </div>
+ </div>
+ <Button
+ size="sm"
+ variant="outline"
+ onClick={() => {
+ // 다운로드 처리
+ const link = document.createElement('a');
+ link.href = attachment.filePath;
+ link.download = attachment.originalFileName;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ }}
+ className="gap-1"
+ >
+ <Download className="h-3 w-3" />
+ 다운로드
+ </Button>
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
</DialogContent>
</Dialog>