"use client" import * as React from "react" import { useForm, useFieldArray } from "react-hook-form" import { z } from "zod" import { zodResolver } from "@hookform/resolvers/zod" import { Download, X, Loader2 } from "lucide-react" import prettyBytes from "pretty-bytes" import { toast } from "sonner" import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from "@/components/ui/sheet" import { Button } from "@/components/ui/button" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Textarea } from "@/components/ui/textarea" import { Dropzone, DropzoneZone, DropzoneUploadIcon, DropzoneTitle, DropzoneDescription, DropzoneInput, } from "@/components/ui/dropzone" import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell, } from "@/components/ui/table" import { formatDate } from "@/lib/utils" import { createRfqCommentWithAttachments } from "@/lib/rfqs/service" export interface MatchedVendorComment { id: number commentText: string commentedBy?: number commentedByEmail?: string createdAt?: Date attachments?: { id: number fileName: string filePath: string }[] } // 1) props 정의 interface CommentSheetProps extends React.ComponentPropsWithRef { initialComments?: MatchedVendorComment[] currentUserId: number rfqId: number vendorId: number onCommentsUpdated?: (comments: MatchedVendorComment[]) => void isLoading?: boolean // New prop } // 2) 폼 스키마 const commentFormSchema = z.object({ commentText: z.string().min(1, "댓글을 입력하세요."), newFiles: z.array(z.any()).optional(), // File[] }) type CommentFormValues = z.infer const MAX_FILE_SIZE = 30e6 // 30MB export function CommentSheet({ rfqId, vendorId, initialComments = [], currentUserId, onCommentsUpdated, isLoading = false, // Default to false ...props }: CommentSheetProps) { console.log(initialComments) const [comments, setComments] = React.useState(initialComments) const [isPending, startTransition] = React.useTransition() React.useEffect(() => { setComments(initialComments) }, [initialComments]) const form = useForm({ resolver: zodResolver(commentFormSchema), defaultValues: { commentText: "", newFiles: [], }, }) const { fields: newFileFields, append, remove } = useFieldArray({ control: form.control, name: "newFiles", }) // (A) 기존 코멘트 렌더링 function renderExistingComments() { if (isLoading) { return (
Loading comments...
) } if (comments.length === 0) { return

No comments yet

} return ( Comment Attachments Created At Created By {comments.map((c) => ( {c.commentText} {!c.attachments?.length && ( No files )} {c.attachments?.length && (
{c.attachments.map((att) => ( ))}
)}
{ c.createdAt ? formatDate(c.createdAt): "-"} {c.commentedByEmail ?? "-"}
))}
) } // (B) 파일 드롭 function handleDropAccepted(files: File[]) { append(files) } // (C) Submit async function onSubmit(data: CommentFormValues) { if (!rfqId) return startTransition(async () => { try { const res = await createRfqCommentWithAttachments({ rfqId, vendorId, commentText: data.commentText, commentedBy: currentUserId, evaluationId: null, cbeId: null, files: data.newFiles, }) if (!res.ok) { throw new Error("Failed to create comment") } toast.success("Comment created") // 임시로 새 코멘트 추가 const newComment: MatchedVendorComment = { id: res.commentId, // 서버 응답 commentText: data.commentText, commentedBy: currentUserId, createdAt: res.createdAt, attachments: data.newFiles?.map((f) => ({ id: Math.floor(Math.random() * 1e6), fileName: f.name, filePath: "/uploads/" + f.name, })) || [], } setComments((prev) => [...prev, newComment]) onCommentsUpdated?.([...comments, newComment]) form.reset() } catch (err: any) { console.error(err) toast.error("Error: " + err.message) } }) } return ( Comments 필요시 첨부파일과 함께 문의/코멘트를 남길 수 있습니다.
{renderExistingComments()}
( New Comment