"use client"; import React, { useState, useCallback, useEffect } from 'react'; import { ScrollArea } from "@/components/ui/scroll-area"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { toast } from "sonner"; import { MessageSquare, Plus, Trash2, Paperclip, X, Save, Upload, FileText, User, Building2, Loader2, Download, } from "lucide-react"; import { cn, formatDateTime } from "@/lib/utils"; import { getAgreementComments, addAgreementComment, deleteAgreementComment, uploadCommentAttachment, deleteCommentAttachment, type AgreementCommentData, type AgreementCommentAuthorType, } from "./actions"; export interface AgreementCommentListProps { basicContractId: number; currentUserType?: AgreementCommentAuthorType; readOnly?: boolean; className?: string; onCommentCountChange?: (count: number) => void; } export function AgreementCommentList({ basicContractId, currentUserType = 'Vendor', readOnly = false, className, onCommentCountChange, }: AgreementCommentListProps) { const [comments, setComments] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isAdding, setIsAdding] = useState(false); const [newComment, setNewComment] = useState(''); const [newAuthorName, setNewAuthorName] = useState(''); const [uploadingFiles, setUploadingFiles] = useState>(new Set()); const [isSaving, setIsSaving] = useState(false); // 코멘트 로드 const loadComments = useCallback(async () => { try { setIsLoading(true); const data = await getAgreementComments(basicContractId); setComments(data); onCommentCountChange?.(data.length); } catch (error) { console.error('코멘트 로드 실패:', error); toast.error("코멘트를 불러오는데 실패했습니다."); } finally { setIsLoading(false); } }, [basicContractId]); // onCommentCountChange를 dependency에서 제거 // 초기 로드 useEffect(() => { loadComments(); }, [basicContractId]); // loadComments 대신 basicContractId만 의존 // 코멘트 추가 핸들러 const handleAddComment = useCallback(async () => { if (!newComment.trim()) { toast.error("코멘트를 입력해주세요."); return; } setIsSaving(true); try { const result = await addAgreementComment({ basicContractId, comment: newComment.trim(), authorName: newAuthorName.trim() || undefined, }); if (result.success) { setNewComment(''); setNewAuthorName(''); setIsAdding(false); toast.success("코멘트가 추가되었습니다."); await loadComments(); // 목록 새로고침 } else { toast.error(result.error || "코멘트 추가에 실패했습니다."); } } catch (error) { console.error('코멘트 추가 실패:', error); toast.error("코멘트 추가에 실패했습니다."); } finally { setIsSaving(false); } }, [newComment, newAuthorName, basicContractId]); // loadComments 제거 // 코멘트 삭제 핸들러 const handleDeleteComment = useCallback(async (commentId: number) => { if (!confirm("이 코멘트를 삭제하시겠습니까?")) { return; } try { const result = await deleteAgreementComment(commentId); if (result.success) { toast.success("코멘트가 삭제되었습니다."); await loadComments(); // 목록 새로고침 } else { toast.error(result.error || "코멘트 삭제에 실패했습니다."); } } catch (error) { console.error('코멘트 삭제 실패:', error); toast.error("코멘트 삭제에 실패했습니다."); } }, []); // loadComments 제거 // 첨부파일 업로드 핸들러 const handleUploadAttachment = useCallback(async (commentId: number, file: File) => { setUploadingFiles(prev => new Set(prev).add(commentId)); try { const result = await uploadCommentAttachment(commentId, file); if (result.success) { toast.success(`${file.name}이(가) 업로드되었습니다.`); await loadComments(); // 목록 새로고침 } else { toast.error(result.error || "파일 업로드에 실패했습니다."); } } catch (error) { console.error('파일 업로드 실패:', error); toast.error("파일 업로드에 실패했습니다."); } finally { setUploadingFiles(prev => { const next = new Set(prev); next.delete(commentId); return next; }); } }, []); // loadComments 제거 // 첨부파일 삭제 핸들러 const handleDeleteAttachment = useCallback(async (commentId: number, attachmentId: string) => { if (!confirm("이 첨부파일을 삭제하시겠습니까?")) { return; } try { const result = await deleteCommentAttachment(commentId, attachmentId); if (result.success) { toast.success("첨부파일이 삭제되었습니다."); await loadComments(); // 목록 새로고침 } else { toast.error(result.error || "첨부파일 삭제에 실패했습니다."); } } catch (error) { console.error('첨부파일 삭제 실패:', error); toast.error("첨부파일 삭제에 실패했습니다."); } }, []); // loadComments 제거 // 파일 크기 포맷팅 const formatFileSize = (bytes: number): string => { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; }; if (isLoading) { return (
); } return (
{/* 헤더 */}

협의 코멘트

총 {comments.length}개 {!readOnly && ( )}

SHI와 협력업체 간 기본계약서 협의 내용을 작성하고 공유합니다.

{/* 코멘트 리스트 */}
{/* 새 코멘트 입력 폼 */} {isAdding && !readOnly && (
{currentUserType === 'SHI' ? ( <> SHI ) : ( <> Vendor )}
setNewAuthorName(e.target.value)} placeholder="작성자 이름을 입력하세요..." className="mt-1.5" />