From 44794a8628997c0d979adb5bd6711cd848b3e397 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 9 Jul 2025 06:27:10 +0000 Subject: (최겸) 기술영업 판교 미팅 이전 rfq-tech 삭제 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/rfqs-tech/table/attachment-rfq-sheet.tsx | 426 --------------------------- 1 file changed, 426 deletions(-) delete mode 100644 lib/rfqs-tech/table/attachment-rfq-sheet.tsx (limited to 'lib/rfqs-tech/table/attachment-rfq-sheet.tsx') diff --git a/lib/rfqs-tech/table/attachment-rfq-sheet.tsx b/lib/rfqs-tech/table/attachment-rfq-sheet.tsx deleted file mode 100644 index d06fae09..00000000 --- a/lib/rfqs-tech/table/attachment-rfq-sheet.tsx +++ /dev/null @@ -1,426 +0,0 @@ -"use client" - -import * as React from "react" -import { z } from "zod" -import { useForm, useFieldArray } from "react-hook-form" -import { zodResolver } from "@hookform/resolvers/zod" - -import { - Sheet, - SheetContent, - SheetHeader, - SheetTitle, - SheetDescription, - SheetFooter, - SheetClose, -} from "@/components/ui/sheet" -import { Button } from "@/components/ui/button" -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - FormDescription -} from "@/components/ui/form" -import { Loader, Download, X, Eye, AlertCircle } from "lucide-react" -import { useToast } from "@/hooks/use-toast" -import { Badge } from "@/components/ui/badge" - -import { - Dropzone, - DropzoneDescription, - DropzoneInput, - DropzoneTitle, - DropzoneUploadIcon, - DropzoneZone, -} from "@/components/ui/dropzone" -import { - FileList, - FileListAction, - FileListDescription, - FileListHeader, - FileListIcon, - FileListInfo, - FileListItem, - FileListName, - FileListSize, -} from "@/components/ui/file-list" - -import prettyBytes from "pretty-bytes" -import { processRfqAttachments } from "../service" -import { format } from "path" -import { formatDate } from "@/lib/utils" -import { RfqWithItemCount } from "@/db/schema/rfq" - -const MAX_FILE_SIZE = 6e8 // 600MB - -/** 기존 첨부 파일 정보 */ -interface ExistingAttachment { - id: number - fileName: string - filePath: string - createdAt?: Date // or Date - vendorId?: number | null - size?: number -} - -/** 새로 업로드할 파일 */ -const newUploadSchema = z.object({ - fileObj: z.any().optional(), // 실제 File -}) - -/** 기존 첨부 (react-hook-form에서 관리) */ -const existingAttachSchema = z.object({ - id: z.number(), - fileName: z.string(), - filePath: z.string(), - vendorId: z.number().nullable().optional(), - createdAt: z.custom().optional(), // or use z.any().optional() - size: z.number().optional(), -}) - -/** RHF 폼 전체 스키마 */ -const attachmentsFormSchema = z.object({ - rfqId: z.number().int(), - existing: z.array(existingAttachSchema), - newUploads: z.array(newUploadSchema), -}) - -type AttachmentsFormValues = z.infer - -interface RfqAttachmentsSheetProps - extends React.ComponentPropsWithRef { - defaultAttachments?: ExistingAttachment[] - rfq: RfqWithItemCount | null - /** 업로드/삭제 후 상위 테이블에 itemCount 등을 업데이트하기 위한 콜백 */ - onAttachmentsUpdated?: (rfqId: number, newItemCount: number) => void -} - -/** - * RfqAttachmentsSheet: - * - 기존 첨부 목록 (다운로드 + 삭제) - * - 새 파일 Dropzone - * - Save 시 processRfqAttachments(server action) - */ -export function RfqAttachmentsSheet({ - defaultAttachments = [], - onAttachmentsUpdated, - rfq, - ...props -}: RfqAttachmentsSheetProps) { - const { toast } = useToast() - const [isPending, startUpdate] = React.useTransition() - const rfqId = rfq?.rfqId ?? 0; - - // 편집 가능 여부 확인 - DRAFT 상태일 때만 편집 가능 - const isEditable = rfq?.status === "DRAFT"; - - // React Hook Form - const form = useForm({ - resolver: zodResolver(attachmentsFormSchema), - defaultValues: { - rfqId, - existing: [], - newUploads: [], - }, - }) - - const { reset, control, handleSubmit } = form - - // defaultAttachments가 바뀔 때마다, RHF 상태를 reset - React.useEffect(() => { - reset({ - rfqId, - existing: defaultAttachments.map((att) => ({ - ...att, - vendorId: att.vendorId ?? null, - size: att.size ?? undefined, - })), - newUploads: [], - }) - }, [rfqId, defaultAttachments, reset]) - - // Field Arrays - const { - fields: existingFields, - remove: removeExisting, - } = useFieldArray({ control, name: "existing" }) - - const { - fields: newUploadFields, - append: appendNewUpload, - remove: removeNewUpload, - } = useFieldArray({ control, name: "newUploads" }) - - // 기존 첨부 항목 중 삭제된 것 찾기 - function findRemovedExistingIds(data: AttachmentsFormValues): number[] { - const finalIds = data.existing.map((att) => att.id) - const originalIds = defaultAttachments.map((att) => att.id) - return originalIds.filter((id) => !finalIds.includes(id)) - } - - async function onSubmit(data: AttachmentsFormValues) { - // 편집 불가능한 상태에서는 제출 방지 - if (!isEditable) return; - - startUpdate(async () => { - try { - const removedExistingIds = findRemovedExistingIds(data) - const newFiles = data.newUploads - .map((it) => it.fileObj) - .filter((f): f is File => !!f) - - // 서버 액션 - const res = await processRfqAttachments({ - rfqId, - removedExistingIds, - newFiles, - vendorId: null, // vendor ID if needed - }) - - if (!res.ok) throw new Error(res.error ?? "Unknown error") - - const newCount = res.updatedItemCount ?? 0 - - toast({ - variant: "default", - title: "Success", - description: "File(s) updated", - }) - - // 상위 테이블 등에 itemCount 업데이트 - onAttachmentsUpdated?.(rfqId, newCount) - - // 모달 닫기 - props.onOpenChange?.(false) - } catch (err) { - toast({ - variant: "destructive", - title: "Error", - description: String(err), - }) - } - }) - } - - /** 기존 첨부 - X 버튼 */ - function handleRemoveExisting(idx: number) { - // 편집 불가능한 상태에서는 삭제 방지 - if (!isEditable) return; - removeExisting(idx) - } - - /** 드롭존에서 파일 받기 */ - function handleDropAccepted(acceptedFiles: File[]) { - // 편집 불가능한 상태에서는 파일 추가 방지 - if (!isEditable) return; - const mapped = acceptedFiles.map((file) => ({ fileObj: file })) - appendNewUpload(mapped) - } - - /** 드롭존에서 파일 거부(에러) */ - function handleDropRejected(fileRejections: any[]) { - // 편집 불가능한 상태에서는 무시 - if (!isEditable) return; - - fileRejections.forEach((rej) => { - toast({ - variant: "destructive", - title: "File Error", - description: rej.file.name + " not accepted", - }) - }) - } - - return ( - - - - - {isEditable ? "Manage Attachments" : "View Attachments"} - {rfq?.status && ( - - {rfq.status} - - )} - - - {`RFQ ${rfq?.rfqCode} - `} - {isEditable ? '파일 첨부/삭제' : '첨부 파일 보기'} - {!isEditable && ( -
- - 드래프트 상태가 아닌 RFQ는 첨부파일을 수정할 수 없습니다. -
- )} -
-
- -
- - {/* 1) 기존 첨부 목록 */} -
-

Existing Attachments

- {existingFields.length === 0 && ( -

No existing attachments

- )} - {existingFields.map((field, index) => { - const vendorLabel = field.vendorId ? "(Vendor)" : "(Internal)" - return ( -
-
- - {field.fileName} {vendorLabel} - - {field.size && ( - - {Math.round(field.size / 1024)} KB - - )} - {field.createdAt && ( - - Created at {formatDate(field.createdAt)} - - )} -
-
- {/* 1) Download button (if filePath) */} - {field.filePath && ( - - - - )} - {/* 2) Remove button - 편집 가능할 때만 표시 */} - {isEditable && ( - - )} -
-
- ) - })} -
- - {/* 2) Dropzone for new uploads - 편집 가능할 때만 표시 */} - {isEditable ? ( - <> - - {({ maxSize }) => ( - ( - - Drop Files Here - - - - -
- -
- Drop to upload - - Max size: {maxSize ? prettyBytes(maxSize) : "??? MB"} - -
-
-
- Alternatively, click browse. - -
- )} - /> - )} -
- - {/* newUpload fields -> FileList */} - {newUploadFields.length > 0 && ( -
-
- {`Files (${newUploadFields.length})`} -
- - {newUploadFields.map((field, idx) => { - const fileObj = form.getValues(`newUploads.${idx}.fileObj`) - if (!fileObj) return null - - const fileName = fileObj.name - const fileSize = fileObj.size - return ( - - - - - {fileName} - - {`${prettyBytes(fileSize)}`} - - - removeNewUpload(idx)}> - - Remove - - - - ) - })} - -
- )} - - ) : ( -
-
- -

보기 모드에서는 파일 첨부를 할 수 없습니다.

-
-
- )} - - - - - - {isEditable && ( - - )} - -
- -
-
- ) -} \ No newline at end of file -- cgit v1.2.3