diff options
| -rw-r--r-- | lib/rfq-last/attachment/rfq-attachments-table.tsx | 4 | ||||
| -rw-r--r-- | lib/rfq-last/attachment/vendor-response-table.tsx | 4 | ||||
| -rw-r--r-- | lib/rfq-last/service.ts | 97 | ||||
| -rw-r--r-- | lib/rfq-last/vendor-response/editor/attachments-upload.tsx | 87 | ||||
| -rw-r--r-- | lib/rfq-last/vendor/rfq-vendor-table.tsx | 2 | ||||
| -rw-r--r-- | lib/rfq-last/vendor/vendor-detail-dialog.tsx | 8 |
6 files changed, 169 insertions, 33 deletions
diff --git a/lib/rfq-last/attachment/rfq-attachments-table.tsx b/lib/rfq-last/attachment/rfq-attachments-table.tsx index d97d32fd..92a89417 100644 --- a/lib/rfq-last/attachment/rfq-attachments-table.tsx +++ b/lib/rfq-last/attachment/rfq-attachments-table.tsx @@ -431,9 +431,9 @@ export function RfqAttachmentsTable({ <DropdownMenuItem onClick={() => handleAction({ type: "download", row })}> 다운로드 </DropdownMenuItem> - <DropdownMenuItem onClick={() => handleAction({ type: "preview", row })}> + {/* <DropdownMenuItem onClick={() => handleAction({ type: "preview", row })}> 미리보기 - </DropdownMenuItem> + </DropdownMenuItem> */} <DropdownMenuSeparator /> <DropdownMenuItem onClick={() => handleAction({ type: "history", row })}> 리비전 히스토리 diff --git a/lib/rfq-last/attachment/vendor-response-table.tsx b/lib/rfq-last/attachment/vendor-response-table.tsx index 47a23d18..8488eea1 100644 --- a/lib/rfq-last/attachment/vendor-response-table.tsx +++ b/lib/rfq-last/attachment/vendor-response-table.tsx @@ -460,10 +460,10 @@ export function VendorResponseTable({ <Download className="mr-2 h-4 w-4" /> 다운로드 </DropdownMenuItem> - <DropdownMenuItem onClick={() => handleAction({ row, type: "preview" })}> + {/* <DropdownMenuItem onClick={() => handleAction({ row, type: "preview" })}> <Eye className="mr-2 h-4 w-4" /> 미리보기 - </DropdownMenuItem> + </DropdownMenuItem> */} </DropdownMenuContent> </DropdownMenu> ); diff --git a/lib/rfq-last/service.ts b/lib/rfq-last/service.ts index f600d04b..50a5c64c 100644 --- a/lib/rfq-last/service.ts +++ b/lib/rfq-last/service.ts @@ -934,6 +934,103 @@ export async function getRfqVendorAttachments(rfqId: number) { } } +/** + * 특정 RFQ의 특정 벤더가 제출한 모든 응답의 첨부파일 조회, 추후 협력업체가 제출한 첨부파일 히스토리 조회용으로 사용 + */ +export async function getVendorResponseAttachments(rfqId: number, vendorId: number) { + try { + if (!rfqId || rfqId <= 0 || !vendorId || vendorId <= 0) { + return { + success: false, + error: "유효하지 않은 RFQ ID 또는 벤더 ID입니다", + data: [] + } + } + + // 먼저 해당 rfq에 연결된 벤더의 모든응답들을 찾기 + const vendorResponses = await db + .select({ + id: rfqLastVendorResponses.id, + status: rfqLastVendorResponses.status, + responseVersion: rfqLastVendorResponses.responseVersion, + }) + .from(rfqLastVendorResponses) + .where( + and( + eq(rfqLastVendorResponses.rfqsLastId, rfqId), + eq(rfqLastVendorResponses.vendorId, vendorId) + ) + ) + + if (vendorResponses.length === 0) { + return { + success: true, + data: [] + } + } + + const responseIds = vendorResponses.map(r => r.id) + + // 해당 응답들의 모든 첨부파일 조회 + const data = await db + .select({ + // 첨부파일 메인 정보 + id: rfqLastVendorAttachments.id, + vendorResponseId: rfqLastVendorAttachments.vendorResponseId, + attachmentType: rfqLastVendorAttachments.attachmentType, + documentNo: rfqLastVendorAttachments.documentNo, + + // 파일 정보 + fileName: rfqLastVendorAttachments.fileName, + originalFileName: rfqLastVendorAttachments.originalFileName, + filePath: rfqLastVendorAttachments.filePath, + fileSize: rfqLastVendorAttachments.fileSize, + fileType: rfqLastVendorAttachments.fileType, + + // 파일 설명 + description: rfqLastVendorAttachments.description, + + // 유효기간 + validFrom: rfqLastVendorAttachments.validFrom, + validTo: rfqLastVendorAttachments.validTo, + + // 업로드 정보 + uploadedBy: rfqLastVendorAttachments.uploadedBy, + uploadedAt: rfqLastVendorAttachments.uploadedAt, + + // 업로더 정보 + uploadedByName: users.name, + + // 응답 정보 + responseStatus: rfqLastVendorResponses.status, + responseVersion: rfqLastVendorResponses.responseVersion, + }) + .from(rfqLastVendorAttachments) + .leftJoin( + rfqLastVendorResponses, + eq(rfqLastVendorAttachments.vendorResponseId, rfqLastVendorResponses.id) + ) + .leftJoin(users, eq(rfqLastVendorAttachments.uploadedBy, users.id)) + .where(inArray(rfqLastVendorAttachments.vendorResponseId, responseIds)) + .orderBy( + desc(rfqLastVendorAttachments.uploadedAt), + rfqLastVendorAttachments.attachmentType + ) + + return { + data, + success: true + } + } catch (err) { + console.error("getVendorResponseAttachments error:", err) + return { + data: [], + success: false, + error: "첨부파일 조회 중 오류가 발생했습니다" + } + } +} + // 벤더 추가 액션 diff --git a/lib/rfq-last/vendor-response/editor/attachments-upload.tsx b/lib/rfq-last/vendor-response/editor/attachments-upload.tsx index b85407ff..96a88ef7 100644 --- a/lib/rfq-last/vendor-response/editor/attachments-upload.tsx +++ b/lib/rfq-last/vendor-response/editor/attachments-upload.tsx @@ -33,7 +33,8 @@ import { FileCheck, Calculator, Wrench, - X + X, + CheckCircle } from "lucide-react" import { formatBytes } from "@/lib/utils" import { cn } from "@/lib/utils" @@ -52,6 +53,7 @@ interface AttachmentsUploadProps { onExistingAttachmentsChange?: (files: any[]) => void responseId?: number userId?: number + isSubmitted?: boolean } const acceptedFileTypes = { @@ -67,7 +69,8 @@ export default function AttachmentsUpload({ existingAttachments = [], onExistingAttachmentsChange, responseId, - userId + userId, + isSubmitted = false }: AttachmentsUploadProps) { const purchaseInputRef = useRef<HTMLInputElement>(null) const designInputRef = useRef<HTMLInputElement>(null) @@ -271,6 +274,16 @@ export default function AttachmentsUpload({ return ( <div className="space-y-4"> + {/* 제출완료 상태 알림 */} + {isSubmitted && ( + <Alert> + <CheckCircle className="h-4 w-4" /> + <AlertDescription> + <strong>제출완료:</strong> 견적서가 이미 제출되어 추가 파일 업로드가 제한됩니다. + </AlertDescription> + </Alert> + )} + {/* 필수 파일 안내 */} <Alert> <AlertCircle className="h-4 w-4" /> @@ -312,13 +325,14 @@ export default function AttachmentsUpload({ <div className={cn( "border-2 border-dashed rounded-lg p-6 text-center transition-colors", - purchaseDragActive ? "border-blue-500 bg-blue-50" : "border-gray-300", - "hover:border-blue-400 hover:bg-blue-50/50" + purchaseDragActive && !isSubmitted ? "border-blue-500 bg-blue-50" : "border-gray-300", + !isSubmitted && "hover:border-blue-400 hover:bg-blue-50/50", + isSubmitted && "opacity-50 cursor-not-allowed bg-gray-50" )} - onDragEnter={handlePurchaseDrag} - onDragLeave={handlePurchaseDrag} - onDragOver={handlePurchaseDrag} - onDrop={handlePurchaseDrop} + onDragEnter={!isSubmitted ? handlePurchaseDrag : undefined} + onDragLeave={!isSubmitted ? handlePurchaseDrag : undefined} + onDragOver={!isSubmitted ? handlePurchaseDrag : undefined} + onDrop={!isSubmitted ? handlePurchaseDrop : undefined} > <Calculator className="mx-auto h-10 w-10 text-blue-500 mb-3" /> <p className="text-sm text-gray-600 mb-2"> @@ -328,11 +342,15 @@ export default function AttachmentsUpload({ type="button" variant="outline" size="sm" - onClick={() => purchaseInputRef.current?.click()} - className="border-blue-500 text-blue-600 hover:bg-blue-50" + onClick={() => !isSubmitted && purchaseInputRef.current?.click()} + className={cn( + "border-blue-500 text-blue-600 hover:bg-blue-50", + isSubmitted && "opacity-50 cursor-not-allowed" + )} + disabled={isSubmitted} > <Paperclip className="h-4 w-4 mr-2" /> - 구매 문서 선택 + {isSubmitted ? "제출완료" : "구매 문서 선택"} </Button> <input ref={purchaseInputRef} @@ -366,13 +384,14 @@ export default function AttachmentsUpload({ <div className={cn( "border-2 border-dashed rounded-lg p-6 text-center transition-colors", - designDragActive ? "border-green-500 bg-green-50" : "border-gray-300", - "hover:border-green-400 hover:bg-green-50/50" + designDragActive && !isSubmitted ? "border-green-500 bg-green-50" : "border-gray-300", + !isSubmitted && "hover:border-green-400 hover:bg-green-50/50", + isSubmitted && "opacity-50 cursor-not-allowed bg-gray-50" )} - onDragEnter={handleDesignDrag} - onDragLeave={handleDesignDrag} - onDragOver={handleDesignDrag} - onDrop={handleDesignDrop} + onDragEnter={!isSubmitted ? handleDesignDrag : undefined} + onDragLeave={!isSubmitted ? handleDesignDrag : undefined} + onDragOver={!isSubmitted ? handleDesignDrag : undefined} + onDrop={!isSubmitted ? handleDesignDrop : undefined} > <Wrench className="mx-auto h-10 w-10 text-green-500 mb-3" /> <p className="text-sm text-gray-600 mb-2"> @@ -382,11 +401,15 @@ export default function AttachmentsUpload({ type="button" variant="outline" size="sm" - onClick={() => designInputRef.current?.click()} - className="border-green-500 text-green-600 hover:bg-green-50" + onClick={() => !isSubmitted && designInputRef.current?.click()} + className={cn( + "border-green-500 text-green-600 hover:bg-green-50", + isSubmitted && "opacity-50 cursor-not-allowed" + )} + disabled={isSubmitted} > <Paperclip className="h-4 w-4 mr-2" /> - 설계 문서 선택 + {isSubmitted ? "제출완료" : "설계 문서 선택"} </Button> <input ref={designInputRef} @@ -494,7 +517,9 @@ export default function AttachmentsUpload({ type="button" variant="ghost" size="sm" - onClick={() => handleDeleteClick(file, true, index)} + onClick={() => !isSubmitted && handleDeleteClick(file, true, index)} + className={isSubmitted ? "opacity-50 cursor-not-allowed" : ""} + disabled={isSubmitted} > <Trash2 className="h-4 w-4 text-red-500" /> </Button> @@ -523,8 +548,12 @@ export default function AttachmentsUpload({ type="button" variant={file.attachmentType === "구매" ? "default" : "ghost"} size="sm" - className="h-7 px-2 text-xs" - onClick={() => handleTypeChange(index, "구매")} + className={cn( + "h-7 px-2 text-xs", + isSubmitted && "opacity-50 cursor-not-allowed" + )} + onClick={() => !isSubmitted && handleTypeChange(index, "구매")} + disabled={isSubmitted} > <Calculator className="h-3 w-3 mr-1" /> 구매 @@ -533,8 +562,12 @@ export default function AttachmentsUpload({ type="button" variant={file.attachmentType === "설계" ? "default" : "ghost"} size="sm" - className="h-7 px-2 text-xs" - onClick={() => handleTypeChange(index, "설계")} + className={cn( + "h-7 px-2 text-xs", + isSubmitted && "opacity-50 cursor-not-allowed" + )} + onClick={() => !isSubmitted && handleTypeChange(index, "설계")} + disabled={isSubmitted} > <Wrench className="h-3 w-3 mr-1" /> 설계 @@ -552,7 +585,9 @@ export default function AttachmentsUpload({ type="button" variant="ghost" size="sm" - onClick={() => handleDeleteClick(file, false, index)} + onClick={() => !isSubmitted && handleDeleteClick(file, false, index)} + className={isSubmitted ? "opacity-50 cursor-not-allowed" : ""} + disabled={isSubmitted} > <Trash2 className="h-4 w-4 text-red-500" /> </Button> diff --git a/lib/rfq-last/vendor/rfq-vendor-table.tsx b/lib/rfq-last/vendor/rfq-vendor-table.tsx index 55549a6d..dc5564e2 100644 --- a/lib/rfq-last/vendor/rfq-vendor-table.tsx +++ b/lib/rfq-last/vendor/rfq-vendor-table.tsx @@ -1599,7 +1599,7 @@ export function RfqVendorTable({ ) : ( <> <Send className="h-4 w-4 mr-2" /> - RFQ 발송 ({shortListCount}) + RFQ 발송 ({selectedRows.length}) </> )} </Button> diff --git a/lib/rfq-last/vendor/vendor-detail-dialog.tsx b/lib/rfq-last/vendor/vendor-detail-dialog.tsx index 08288dd6..7946e371 100644 --- a/lib/rfq-last/vendor/vendor-detail-dialog.tsx +++ b/lib/rfq-last/vendor/vendor-detail-dialog.tsx @@ -595,6 +595,7 @@ export function VendorResponseDetailDialog({ <TableHead>단위</TableHead> <TableHead className="text-right">단가</TableHead> <TableHead className="text-right">금액</TableHead> + <TableHead>통화</TableHead> <TableHead>납기일</TableHead> </TableRow> </TableHeader> @@ -613,6 +614,9 @@ export function VendorResponseDetailDialog({ {new Intl.NumberFormat("ko-KR").format(item.totalPrice)} </TableCell> <TableCell> + {response?.pricing?.vendorCurrency || data.currency || item.currency} + </TableCell> + <TableCell> {item.vendorDeliveryDate ? format(new Date(item.vendorDeliveryDate), "MM-dd") : "-"} @@ -662,7 +666,7 @@ export function VendorResponseDetailDialog({ </div> </div> <div className="flex items-center gap-2"> - <Button + {/* <Button variant="ghost" size="sm" onClick={() => { @@ -671,7 +675,7 @@ export function VendorResponseDetailDialog({ }} > <Eye className="h-4 w-4" /> - </Button> + </Button> */} <Button variant="ghost" size="sm" |
