"use client" import * as React from "react" import { ChevronsUpDown, MessagesSquare, Download, Loader2 } from "lucide-react" import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Card } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { PQGroupData } from "@/lib/pq/service" import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Textarea } from "@/components/ui/textarea" import { addReviewCommentAction, getItemReviewLogsAction } from "@/lib/pq/service" import { useToast } from "@/hooks/use-toast" import { formatDate } from "@/lib/utils" import { downloadFileAction } from "@/lib/downloadFile" import { useSession } from "next-auth/react" interface ReviewLog { id: number reviewerComment: string reviewerName: string | null createdAt: Date } interface VendorPQReviewPageProps { data: PQGroupData[]; onCommentAdded?: () => void; // 코멘트 추가 콜백 } export default function VendorPQReviewPage({ data, onCommentAdded }: VendorPQReviewPageProps) { const { toast } = useToast() // 파일 다운로드 함수 - 서버 액션 사용 const handleFileDownload = async (filePath: string, fileName: string) => { try { toast({ title: "Download Started", description: `Preparing ${fileName} for download...`, }); // 서버 액션 호출 const result = await downloadFileAction(filePath); if (!result.ok || !result.data) { throw new Error(result.error || 'Failed to download file'); } // Base64 디코딩하여 Blob 생성 const binaryString = atob(result.data.content); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } // Blob 생성 및 다운로드 const blob = new Blob([bytes.buffer], { type: result.data.mimeType }); const url = URL.createObjectURL(blob); // 다운로드 링크 생성 및 클릭 const a = document.createElement('a'); a.href = url; a.download = fileName; document.body.appendChild(a); a.click(); // 정리 URL.revokeObjectURL(url); document.body.removeChild(a); toast({ title: "Download Complete", description: `${fileName} downloaded successfully`, }); } catch (error) { console.error('Download error:', error); toast({ title: "Download Error", description: error instanceof Error ? error.message : "Failed to download file", variant: "destructive" }); } }; return (
{data.map((group) => (

{group.groupName}

Code Check Point Answer Attachments Comments {group.items.map((item) => ( {item.code} {item.checkPoint} {item.answer ? (

{item.answer}

) : (

(no answer)

)}
{item.attachments.length > 0 ? (
    {item.attachments.map((file) => (
  • ))}
) : (

(none)

)}
))}
))}
) } interface ItemReviewButtonProps { answerId?: number; checkPoint: string; // Check Point 추가 onCommentAdded?: () => void; } /** * A button that opens a dialog to show logs + add new comment for a single item (vendorPqCriteriaAnswers). */ function ItemReviewButton({ answerId, checkPoint, onCommentAdded }: ItemReviewButtonProps) { const { toast } = useToast(); const [open, setOpen] = React.useState(false); const [logs, setLogs] = React.useState([]); const [newComment, setNewComment] = React.useState(""); const [isLoading, setIsLoading] = React.useState(false); const [hasComments, setHasComments] = React.useState(false); const { data: session } = useSession() const reviewerName = session?.user?.name || "Unknown Reviewer" const reviewerId = session?.user?.id // If there's no answerId, item wasn't answered if (!answerId) { return

N/A

; } // fetchLogs 함수를 useCallback으로 메모이제이션 const fetchLogs = React.useCallback(async () => { try { setIsLoading(true); const res = await getItemReviewLogsAction({ answerId }); if (res.ok && res.data) { setLogs(res.data); // 코멘트 존재 여부 설정 setHasComments(res.data.length > 0); } else { console.error("Error response:", res.error); toast({ title: "Error", description: res.error, variant: "destructive" }); } } catch (error) { console.error("Fetch error:", error); toast({ title: "Error", description: String(error), variant: "destructive" }); } finally { setIsLoading(false); } }, [answerId, toast]); // 초기 로드 시 코멘트 존재 여부 확인 (아이콘 색상용) React.useEffect(() => { const checkComments = async () => { try { const res = await getItemReviewLogsAction({ answerId }); if (res.ok && res.data) { setHasComments(res.data.length > 0); } } catch (error) { console.error("Error checking comments:", error); } }; checkComments(); }, [answerId]); // open 상태가 변경될 때 로그 가져오기 React.useEffect(() => { if (open) { fetchLogs(); } }, [open, fetchLogs]); // 버튼 클릭 핸들러 - 다이얼로그 열기 const handleButtonClick = React.useCallback(() => { setOpen(true); }, []); // 다이얼로그 상태 변경 핸들러 const handleOpenChange = React.useCallback((nextOpen: boolean) => { setOpen(nextOpen); }, []); // 코멘트 추가 핸들러 const handleAddComment = React.useCallback(async () => { try { setIsLoading(true); const res = await addReviewCommentAction({ answerId, comment: newComment, reviewerName, }); if (res.ok) { toast({ title: "Comment added", description: "New review comment saved" }); setNewComment(""); setHasComments(true); // 코멘트 추가 성공 시 상태 업데이트 // 코멘트가 추가되었음을 부모 컴포넌트에 알림 if (onCommentAdded) { onCommentAdded(); } // 로그 다시 가져오기 fetchLogs(); } else { toast({ title: "Error", description: res.error, variant: "destructive" }); } } catch (error) { toast({ title: "Error", description: String(error), variant: "destructive" }); } finally { setIsLoading(false); } }, [answerId, newComment, onCommentAdded, fetchLogs, toast]); return ( <> {checkPoint} Comments {/* Logs section */}
{isLoading ? (
) : logs.length > 0 ? ( logs.map((log) => (

{log.reviewerName}

{log.reviewerComment}

{formatDate(log.createdAt, "KR")}

)) ) : (

No comments yet.

)}
{/* Add new comment */}