diff options
Diffstat (limited to 'lib/rfq-last/vendor-response/editor')
| -rw-r--r-- | lib/rfq-last/vendor-response/editor/attachments-upload.tsx | 134 | ||||
| -rw-r--r-- | lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx | 50 |
2 files changed, 169 insertions, 15 deletions
diff --git a/lib/rfq-last/vendor-response/editor/attachments-upload.tsx b/lib/rfq-last/vendor-response/editor/attachments-upload.tsx index a2967767..ea7bb9c9 100644 --- a/lib/rfq-last/vendor-response/editor/attachments-upload.tsx +++ b/lib/rfq-last/vendor-response/editor/attachments-upload.tsx @@ -1,11 +1,20 @@ "use client" import { useState, useRef } from "react" +import { useSession } from "next-auth/react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Alert, AlertDescription } from "@/components/ui/alert" import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { Table, TableBody, TableCell, @@ -23,10 +32,13 @@ import { Paperclip, FileCheck, Calculator, - Wrench + Wrench, + X } from "lucide-react" import { formatBytes } from "@/lib/utils" import { cn } from "@/lib/utils" +import { toast } from "sonner" +import { deleteVendorResponseAttachment } from "../../service" interface FileWithType extends File { attachmentType?: "구매" | "설계" @@ -37,6 +49,9 @@ interface AttachmentsUploadProps { attachments: FileWithType[] onAttachmentsChange: (files: FileWithType[]) => void existingAttachments?: any[] + onExistingAttachmentsChange?: (files: any[]) => void + responseId?: number + userId?: number } const acceptedFileTypes = { @@ -49,13 +64,18 @@ const acceptedFileTypes = { export default function AttachmentsUpload({ attachments, onAttachmentsChange, - existingAttachments = [] + existingAttachments = [], + onExistingAttachmentsChange, + responseId, + userId }: AttachmentsUploadProps) { const purchaseInputRef = useRef<HTMLInputElement>(null) const designInputRef = useRef<HTMLInputElement>(null) const [purchaseDragActive, setPurchaseDragActive] = useState(false) const [designDragActive, setDesignDragActive] = useState(false) const [uploadErrors, setUploadErrors] = useState<string[]>([]) + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) + const [fileToDelete, setFileToDelete] = useState<{file: any, isExisting: boolean, index: number} | null>(null) // 파일 유효성 검사 const validateFile = (file: File): string | null => { @@ -158,6 +178,57 @@ export default function AttachmentsUpload({ newFiles[index].attachmentType = newType onAttachmentsChange(newFiles) } + + // 파일 삭제 확인 + const handleDeleteClick = (file: any, isExisting: boolean, index: number) => { + setFileToDelete({ file, isExisting, index }) + setDeleteDialogOpen(true) + } + + // 파일 삭제 실행 + const handleDeleteConfirm = async () => { + if (!fileToDelete) return + + const { isExisting, index } = fileToDelete + + if (isExisting) { + // 기존 첨부파일 삭제 - 서버액션 호출 + if (responseId && userId && fileToDelete.file.id) { + try { + const result = await deleteVendorResponseAttachment({ + attachmentId: fileToDelete.file.id, + responseId, + userId + }) + if (result.success) { + // 클라이언트 상태 업데이트 + const newExistingAttachments = existingAttachments.filter((_, i) => i !== index) + onExistingAttachmentsChange?.(newExistingAttachments) + } else { + toast.error(`삭제 실패: ${result.error}`) + return + } + } catch (error) { + console.error('삭제 API 호출 실패:', error) + toast.error('삭제 중 오류가 발생했습니다.') + return + } + } + } else { + // 새 첨부파일 삭제 (클라이언트에서만) + const newFiles = attachments.filter((_, i) => i !== index) + onAttachmentsChange(newFiles) + } + + setDeleteDialogOpen(false) + setFileToDelete(null) + } + + // 파일 삭제 취소 + const handleDeleteCancel = () => { + setDeleteDialogOpen(false) + setFileToDelete(null) + } // 파일 아이콘 가져오기 const getFileIcon = (fileName: string) => { @@ -388,14 +459,24 @@ export default function AttachmentsUpload({ </Badge> </TableCell> <TableCell> - <Button - type="button" - variant="ghost" - size="sm" - onClick={() => window.open(file.filePath, '_blank')} - > - <Download className="h-4 w-4" /> - </Button> + <div className="flex items-center gap-1"> + <Button + type="button" + variant="ghost" + size="sm" + onClick={() => window.open(file.filePath, '_blank')} + > + <Download className="h-4 w-4" /> + </Button> + <Button + type="button" + variant="ghost" + size="sm" + onClick={() => handleDeleteClick(file, true, index)} + > + <Trash2 className="h-4 w-4 text-red-500" /> + </Button> + </div> </TableCell> </TableRow> ))} @@ -449,7 +530,7 @@ export default function AttachmentsUpload({ type="button" variant="ghost" size="sm" - onClick={() => handleFileRemove(index)} + onClick={() => handleDeleteClick(file, false, index)} > <Trash2 className="h-4 w-4 text-red-500" /> </Button> @@ -461,6 +542,37 @@ export default function AttachmentsUpload({ </CardContent> </Card> )} + + {/* 파일 삭제 확인 다이얼로그 */} + <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}> + <DialogContent> + <DialogHeader> + <DialogTitle>파일 삭제</DialogTitle> + <DialogDescription> + {fileToDelete?.isExisting ? '기존 첨부파일' : '새로 업로드한 파일'} "{fileToDelete?.file.originalFileName || fileToDelete?.file.name}"을(를) 삭제하시겠습니까? + <br /> + <strong>삭제된 파일은 복구할 수 없습니다.</strong> + </DialogDescription> + </DialogHeader> + <DialogFooter> + <Button + type="button" + variant="outline" + onClick={handleDeleteCancel} + > + 취소 + </Button> + <Button + type="button" + variant="destructive" + onClick={handleDeleteConfirm} + > + <Trash2 className="h-4 w-4 mr-2" /> + 삭제 + </Button> + </DialogFooter> + </DialogContent> + </Dialog> </div> ) }
\ No newline at end of file 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 569546dd..fec9a2b9 100644 --- a/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx +++ b/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx @@ -14,6 +14,11 @@ import RfqInfoHeader from "./rfq-info-header" import CommercialTermsForm from "./commercial-terms-form" import QuotationItemsTable from "./quotation-items-table" import AttachmentsUpload from "./attachments-upload" + +interface FileWithType extends File { + attachmentType?: "구매" | "설계" + description?: string +} import { formatDate, formatCurrency } from "@/lib/utils" import { Shield, FileText, CheckCircle, XCircle, Clock, Download, Eye, Save, Send, AlertCircle, Upload, } from "lucide-react" import { Progress } from "@/components/ui/progress" @@ -103,11 +108,34 @@ export default function VendorResponseEditor({ const router = useRouter() const [loading, setLoading] = useState(false) const [activeTab, setActiveTab] = useState("info") - const [attachments, setAttachments] = useState<File[]>([]) + const [attachments, setAttachments] = useState<FileWithType[]>([]) + const [existingAttachments, setExistingAttachments] = useState<any[]>([]) + const [deletedAttachments, setDeletedAttachments] = useState<any[]>([]) const [uploadProgress, setUploadProgress] = useState(0) // 추가 console.log(existingResponse,"existingResponse") + // existingResponse가 변경될 때 existingAttachments 초기화 + useEffect(() => { + if (existingResponse?.attachments) { + setExistingAttachments([...existingResponse.attachments]) + setDeletedAttachments([]) // 삭제 목록 초기화 + } else { + setExistingAttachments([]) + setDeletedAttachments([]) + } + }, [existingResponse?.attachments]) + + // 기존 첨부파일 삭제 처리 + const handleExistingAttachmentsChange = (files: any[]) => { + const currentAttachments = existingResponse?.attachments || [] + const deleted = currentAttachments.filter( + curr => !files.some(f => f.id === curr.id) + ) + setExistingAttachments(files) + setDeletedAttachments(prev => [...prev, ...deleted]) + } + // Form 초기값 설정 const defaultValues: VendorResponseFormData = { @@ -229,10 +257,20 @@ export default function VendorResponseEditor({ try { const formData = new FormData() - const fileMetadata = attachments.map((file: any) => ({ + const fileMetadata = attachments.map((file: FileWithType) => ({ attachmentType: file.attachmentType || "기타", description: file.description || "" })) + + // 삭제된 첨부파일 ID 목록 + const deletedAttachmentIds = deletedAttachments.map(file => file.id) + + // 디버그: 첨부파일 attachmentType 확인 + console.log('Attachments with types:', attachments.map(f => ({ + name: f.name, + attachmentType: f.attachmentType, + size: f.size + }))) // 기본 데이터 추가 @@ -246,7 +284,8 @@ export default function VendorResponseEditor({ submittedBy: isSubmit ? userId : null, totalAmount: data.quotationItems.reduce((sum, item) => sum + item.totalPrice, 0), updatedBy: userId, - fileMetadata + fileMetadata, + deletedAttachmentIds } console.log('Submitting data:', submitData) // 디버깅용 @@ -468,7 +507,10 @@ export default function VendorResponseEditor({ <AttachmentsUpload attachments={attachments} onAttachmentsChange={setAttachments} - existingAttachments={existingResponse?.attachments} + existingAttachments={existingAttachments} + onExistingAttachmentsChange={handleExistingAttachmentsChange} + responseId={existingResponse?.id} + userId={userId} /> </TabsContent> </Tabs> |
