"use client"; import React, { useState, useCallback } 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, } from "lucide-react"; import { cn, formatDateTime } from "@/lib/utils"; export type ReviewCommentAuthorType = 'SHI' | 'Vendor'; export interface ReviewComment { id: string; authorType: ReviewCommentAuthorType; authorName?: string; comment: string; attachments?: ReviewAttachment[]; createdAt: Date; updatedAt?: Date; } export interface ReviewAttachment { id: string; fileName: string; filePath: string; fileSize: number; uploadedAt: Date; } export interface ClauseReviewListProps { documentId?: number; comments: ReviewComment[]; onAddComment?: (comment: Omit) => Promise; onDeleteComment?: (commentId: string) => Promise; onUploadAttachment?: (commentId: string, file: File) => Promise; onDeleteAttachment?: (commentId: string, attachmentId: string) => Promise; filterAuthorType?: ReviewCommentAuthorType | 'ALL'; readOnly?: boolean; currentUserType?: ReviewCommentAuthorType; className?: string; } export function ClauseReviewList({ documentId, comments, onAddComment, onDeleteComment, onUploadAttachment, onDeleteAttachment, filterAuthorType = 'ALL', readOnly = false, currentUserType = 'Vendor', className, }: ClauseReviewListProps) { 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 filteredComments = React.useMemo(() => { if (filterAuthorType === 'ALL') return comments; return comments.filter(c => c.authorType === filterAuthorType); }, [comments, filterAuthorType]); // 코멘트 추가 핸들러 const handleAddComment = useCallback(async () => { if (!newComment.trim()) { toast.error("코멘트를 입력해주세요."); return; } if (!onAddComment) { toast.error("코멘트 추가 기능이 활성화되지 않았습니다."); return; } setIsSaving(true); try { await onAddComment({ authorType: currentUserType, authorName: newAuthorName.trim() || undefined, comment: newComment.trim(), attachments: [], }); setNewComment(''); setNewAuthorName(''); setIsAdding(false); toast.success("코멘트가 추가되었습니다."); } catch (error) { console.error('코멘트 추가 실패:', error); toast.error("코멘트 추가에 실패했습니다."); } finally { setIsSaving(false); } }, [newComment, newAuthorName, currentUserType, onAddComment]); // 코멘트 삭제 핸들러 const handleDeleteComment = useCallback(async (commentId: string) => { if (!onDeleteComment) return; try { await onDeleteComment(commentId); toast.success("코멘트가 삭제되었습니다."); } catch (error) { console.error('코멘트 삭제 실패:', error); toast.error("코멘트 삭제에 실패했습니다."); } }, [onDeleteComment]); // 첨부파일 업로드 핸들러 const handleUploadAttachment = useCallback(async (commentId: string, file: File) => { if (!onUploadAttachment) { toast.error("첨부파일 업로드 기능이 활성화되지 않았습니다."); return; } setUploadingFiles(prev => new Set(prev).add(commentId)); try { await onUploadAttachment(commentId, file); toast.success(`${file.name}이(가) 업로드되었습니다.`); } catch (error) { console.error('파일 업로드 실패:', error); toast.error("파일 업로드에 실패했습니다."); } finally { setUploadingFiles(prev => { const next = new Set(prev); next.delete(commentId); return next; }); } }, [onUploadAttachment]); // 첨부파일 삭제 핸들러 const handleDeleteAttachment = useCallback(async (commentId: string, attachmentId: string) => { if (!onDeleteAttachment) return; try { await onDeleteAttachment(commentId, attachmentId); toast.success("첨부파일이 삭제되었습니다."); } catch (error) { console.error('첨부파일 삭제 실패:', error); toast.error("첨부파일 삭제에 실패했습니다."); } }, [onDeleteAttachment]); // 파일 크기 포맷팅 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'; }; return (
{/* 헤더 */}

협의 코멘트

총 {filteredComments.length}개 {!readOnly && ( )}
{/* 안내 메시지 */}

{filterAuthorType === 'SHI' ? 'SHI(삼성중공업) 코멘트만 표시됩니다.' : filterAuthorType === 'Vendor' ? '협력업체 코멘트만 표시됩니다.' : '모든 코멘트를 표시합니다.'}

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