summaryrefslogtreecommitdiff
path: root/components/pq/pq-review-detail.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/pq/pq-review-detail.tsx')
-rw-r--r--components/pq/pq-review-detail.tsx888
1 files changed, 0 insertions, 888 deletions
diff --git a/components/pq/pq-review-detail.tsx b/components/pq/pq-review-detail.tsx
deleted file mode 100644
index 4f897a2b..00000000
--- a/components/pq/pq-review-detail.tsx
+++ /dev/null
@@ -1,888 +0,0 @@
-"use client"
-
-import React from "react"
-import { Button } from "@/components/ui/button"
-import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"
-import { Textarea } from "@/components/ui/textarea"
-import { useToast } from "@/hooks/use-toast"
-import {
- PQGroupData,
- requestPqChangesAction,
- updateVendorStatusAction,
- updateProjectPQStatusAction,
- getItemReviewLogsAction
-} from "@/lib/pq/service"
-import { Vendor } from "@/db/schema/vendors"
-import { Separator } from "@/components/ui/separator"
-import { Badge } from "@/components/ui/badge"
-import { ChevronsUpDown, MessagesSquare, Download, Loader2, X } 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 { formatDate } from "@/lib/utils"
-import { downloadFileAction } from "@/lib/downloadFile"
-import { useSession } from "next-auth/react" // Importando o hook do next-auth
-
-// 코멘트 상태를 위한 인터페이스 정의
-interface PendingComment {
- answerId: number;
- checkPoint: string;
- code: string;
- comment: string;
- createdAt: Date;
-}
-
-interface ReviewLog {
- id: number
- reviewerComment: string
- reviewerName: string | null
- createdAt: Date
-}
-
-// Updated props interface to support both general and project PQs
-interface VendorPQAdminReviewProps {
- data: PQGroupData[]
- vendor: Vendor
- projectId?: number
- projectName?: string
- projectStatus?: string
- // loadData: () => Promise<PQGroupData[]>
- loadData: (vendorId: number, projectId?: number) => Promise<PQGroupData[]>
-
- pqType: 'general' | 'project'
-}
-
-export default function VendorPQAdminReview({
- data,
- vendor,
- projectId,
- projectName,
- projectStatus,
- loadData,
- pqType
-}: VendorPQAdminReviewProps) {
- const { toast } = useToast()
- const { data: session } = useSession()
- const reviewerName = session?.user?.name || "Unknown Reviewer"
- const reviewerId = session?.user?.id
-
-
- // State for dynamically loaded data
- const [pqData, setPqData] = React.useState<PQGroupData[]>(data)
- const [isDataLoading, setIsDataLoading] = React.useState(false)
-
- // Load data if not provided initially (for tab switching)
- React.useEffect(() => {
- if (data.length === 0) {
- const fetchData = async () => {
- setIsDataLoading(true)
- try {
- const freshData = await loadData(vendor.id, projectId)
-
- setPqData(freshData)
- } catch (error) {
- console.error("Error loading PQ data:", error)
- toast({
- title: "Error",
- description: "Failed to load PQ data",
- variant: "destructive"
- })
- } finally {
- setIsDataLoading(false)
- }
- }
- fetchData()
- } else {
- setPqData(data)
- }
- }, [data, loadData, vendor.id, projectId, toast])
-
- // 다이얼로그 상태들
- const [showRequestDialog, setShowRequestDialog] = React.useState(false)
- const [showApproveDialog, setShowApproveDialog] = React.useState(false)
- const [showRejectDialog, setShowRejectDialog] = React.useState(false)
-
- // 코멘트 상태들
- const [requestComment, setRequestComment] = React.useState("")
- const [approveComment, setApproveComment] = React.useState("")
- const [rejectComment, setRejectComment] = React.useState("")
- const [isLoading, setIsLoading] = React.useState(false)
-
- // 항목별 코멘트 상태 추적 (메모리에만 저장)
- const [pendingComments, setPendingComments] = React.useState<PendingComment[]>([])
-
- // 코멘트 추가 핸들러 - 실제 서버 저장이 아닌 메모리에 저장
- const handleCommentAdded = (newComment: PendingComment) => {
- setPendingComments(prev => [...prev, newComment]);
- toast({
- title: "Comment Added",
- description: `Comment added for ${newComment.code}. Please "Request Changes" to save.`
- });
- }
-
- // 코멘트 삭제 핸들러
- const handleRemoveComment = (index: number) => {
- setPendingComments(prev => prev.filter((_, i) => i !== index));
- }
-
- // 1) 승인 다이얼로그 표시
- const handleApprove = () => {
- // 코멘트가 있는데 승인하려고 하면 경고
- if (pendingComments.length > 0) {
- if (!confirm('You have unsaved comments. Are you sure you want to approve without requesting changes?')) {
- return;
- }
- }
- setShowApproveDialog(true)
- }
-
- // 실제 승인 처리 - 일반 PQ와 프로젝트 PQ 분리
- const handleSubmitApprove = async () => {
- try {
- setIsLoading(true)
- setShowApproveDialog(false)
-
- let res;
-
- if (pqType === 'general') {
- // 일반 PQ 승인
- res = await updateVendorStatusAction(vendor.id, "PQ_APPROVED")
- } else if (projectId) {
- // 프로젝트 PQ 승인
- res = await updateProjectPQStatusAction({
- vendorId: vendor.id,
- projectId,
- status: "APPROVED",
- comment: approveComment.trim() || undefined
- })
- }
-
- if (res?.ok) {
- toast({
- title: "Approved",
- description: `${pqType === 'general' ? 'General' : 'Project'} PQ has been approved.`
- })
- // 코멘트 초기화
- setPendingComments([]);
- } else {
- toast({
- title: "Error",
- description: res?.error || "An error occurred",
- variant: "destructive"
- })
- }
- } catch (error) {
- toast({ title: "Error", description: String(error), variant: "destructive" })
- } finally {
- setIsLoading(false)
- setApproveComment("")
- }
- }
-
- // 2) 거부 다이얼로그 표시
- const handleReject = () => {
- // 코멘트가 있는데 거부하려고 하면 경고
- if (pendingComments.length > 0) {
- if (!confirm('You have unsaved comments. Are you sure you want to reject without requesting changes?')) {
- return;
- }
- }
- setShowRejectDialog(true)
- }
-
- // 실제 거부 처리 - 일반 PQ와 프로젝트 PQ 분리
- const handleSubmitReject = async () => {
- try {
- setIsLoading(true)
- setShowRejectDialog(false)
-
- if (!rejectComment.trim()) {
- toast({
- title: "Error",
- description: "Please provide a reason for rejection",
- variant: "destructive"
- })
- return;
- }
-
- let res;
-
- if (pqType === 'general') {
- // 일반 PQ 거부
- res = await updateVendorStatusAction(vendor.id, "REJECTED")
- } else if (projectId) {
- // 프로젝트 PQ 거부
- res = await updateProjectPQStatusAction({
- vendorId: vendor.id,
- projectId,
- status: "REJECTED",
- comment: rejectComment
- })
- }
-
- if (res?.ok) {
- toast({
- title: "Rejected",
- description: `${pqType === 'general' ? 'General' : 'Project'} PQ has been rejected.`
- })
- // 코멘트 초기화
- setPendingComments([]);
- } else {
- toast({
- title: "Error",
- description: res?.error || "An error occurred",
- variant: "destructive"
- })
- }
- } catch (error) {
- toast({ title: "Error", description: String(error), variant: "destructive" })
- } finally {
- setIsLoading(false)
- setRejectComment("")
- }
- }
-
- // 3) 변경 요청 다이얼로그 표시
- const handleRequestChanges = () => {
- setShowRequestDialog(true)
- }
-
- // 4) 변경 요청 처리 - 이제 프로젝트 ID 포함
- const handleSubmitRequestChanges = async () => {
- try {
- setIsLoading(true);
- setShowRequestDialog(false);
-
- // 항목별 코멘트 준비 - answerId와 함께 checkPoint와 code도 전송
- const itemComments = pendingComments.map(pc => ({
- answerId: pc.answerId,
- checkPoint: pc.checkPoint,
- code: pc.code,
- comment: pc.comment
- }));
-
- // 서버 액션 호출 (프로젝트 ID 추가)
- const res = await requestPqChangesAction({
- vendorId: vendor.id,
- projectId: pqType === 'project' ? projectId : undefined,
- comment: itemComments,
- generalComment: requestComment || undefined,
- reviewerName,
- reviewerId
- });
-
- if (res.ok) {
- toast({
- title: "Changes Requested",
- description: `${pqType === 'general' ? 'Vendor' : 'Project'} was notified of your comments.`,
- });
- // 코멘트 초기화
- setPendingComments([]);
- } else {
- toast({
- title: "Error",
- description: res.error,
- variant: "destructive"
- });
- }
- } catch (error) {
- toast({
- title: "Error",
- description: String(error),
- variant: "destructive"
- });
- } finally {
- setIsLoading(false);
- setRequestComment("");
- }
- };
-
- // 현재 상태에 따른 액션 버튼 비활성화 여부 판단
- const getDisabledState = () => {
- if (pqType === 'general') {
- // 일반 PQ는 vendor 상태에 따라 결정
- return vendor.status === 'PQ_APPROVED' || vendor.status === 'APPROVED';
- } else if (pqType === 'project' && projectStatus) {
- // 프로젝트 PQ는 project 상태에 따라 결정
- return projectStatus === 'APPROVED' || projectStatus === 'REJECTED';
- }
- return false;
- };
-
- const areActionsDisabled = getDisabledState();
-
- return (
- <div className="space-y-4">
- {/* PQ Type indicators and status */}
- {pqType === 'project' && projectName && (
- <div className="flex flex-col space-y-1 mb-4">
- <div className="flex items-center gap-2">
- <Badge variant="outline">{projectName}</Badge>
- {projectStatus && (
- <Badge className={
- projectStatus === 'APPROVED' ? 'bg-green-100 text-green-800' :
- projectStatus === 'REJECTED' ? 'bg-red-100 text-red-800' :
- 'bg-blue-100 text-blue-800'
- }>
- {projectStatus}
- </Badge>
- )}
- </div>
- {areActionsDisabled && (
- <p className="text-sm text-muted-foreground">
- This PQ has already been {
- pqType !== 'project'
- ? (vendor.status === 'PQ_APPROVED' || vendor.status === 'APPROVED' ? 'approved' : 'rejected')
- : (projectStatus === 'APPROVED' ? 'approved' : 'rejected')
- }. No further actions can be taken.
- </p>
- )}
- </div>
- )}
-
- {/* Loading indicator */}
- {isDataLoading && (
- <div className="flex justify-center items-center h-32">
- <Loader2 className="h-8 w-8 animate-spin text-primary" />
- </div>
- )}
-
- {!isDataLoading && (
- <>
- {/* Top header */}
- <div className="flex items-center justify-between">
- <h2 className="text-2xl font-bold">
- {vendor.vendorCode} - {vendor.vendorName} {pqType === 'project' ? 'Project' : 'General'} PQ Review
- </h2>
- <div className="flex gap-2">
- <Button
- variant="outline"
- disabled={isLoading || areActionsDisabled}
- onClick={handleReject}
- >
- Reject
- </Button>
- <Button
- variant={pendingComments.length > 0 ? "default" : "outline"}
- disabled={isLoading || areActionsDisabled}
- onClick={handleRequestChanges}
- >
- Request Changes
- {pendingComments.length > 0 && (
- <span className="ml-2 bg-white text-primary rounded-full h-5 min-w-5 inline-flex items-center justify-center text-xs px-1">
- {pendingComments.length}
- </span>
- )}
- </Button>
- <Button
- disabled={isLoading || areActionsDisabled}
- onClick={handleApprove}
- >
- Approve
- </Button>
- </div>
- </div>
-
- <p className="text-sm text-muted-foreground">
- Review the submitted PQ items below, then approve, reject, or request more info.
- </p>
-
- {/* 코멘트가 있을 때 알림 표시 */}
- {pendingComments.length > 0 && (
- <div className="mt-4 p-3 bg-yellow-50 border border-yellow-200 rounded-md text-yellow-800">
- <p className="text-sm font-medium flex items-center">
- <span className="mr-2">⚠️</span>
- You have {pendingComments.length} pending comments. Click "Request Changes" to save them.
- </p>
- </div>
- )}
-
- <Separator />
-
- {/* PQ 데이터 표시 */}
- {pqData.length > 0 ? (
- <VendorPQReviewPageIntegrated
- data={pqData}
- onCommentAdded={handleCommentAdded}
- />
- ) : (
- <div className="text-center py-10">
- <p className="text-muted-foreground">No PQ data available for review.</p>
- </div>
- )}
- </>
- )}
-
- {/* 변경 요청 다이얼로그 */}
- <Dialog open={showRequestDialog} onOpenChange={setShowRequestDialog}>
- <DialogContent className="max-w-3xl">
- <DialogHeader>
- <DialogTitle>Request PQ Changes</DialogTitle>
- <DialogDescription>
- Review your comments and add any additional notes. The vendor will receive these changes.
- </DialogDescription>
- </DialogHeader>
-
- {/* 항목별 코멘트 목록 */}
- {pendingComments.length > 0 && (
- <div className="border rounded-md p-2 space-y-2 max-h-[300px] overflow-y-auto">
- <h3 className="font-medium text-sm">Item Comments:</h3>
- {pendingComments.map((comment, index) => (
- <div key={index} className="flex items-start gap-2 p-2 border rounded-md bg-muted/50">
- <div className="flex-1">
- <div className="flex items-center gap-2">
- <span className="text-sm font-medium">{comment.code}</span>
- <span className="text-sm">{comment.checkPoint}</span>
- </div>
- <p className="text-sm mt-1">{comment.comment}</p>
- <p className="text-xs text-muted-foreground mt-1">
- {formatDate(comment.createdAt, "KR")}
- </p>
- </div>
- <Button
- variant="ghost"
- size="sm"
- className="p-0 h-8 w-8"
- onClick={() => handleRemoveComment(index)}
- >
- <X className="h-4 w-4" />
- </Button>
- </div>
- ))}
- </div>
- )}
-
- {/* 추가 코멘트 입력 */}
- <div className="space-y-2 mt-2">
- <label className="text-sm font-medium">
- {pendingComments.length > 0
- ? "Additional comments (optional):"
- : "Enter details about what should be modified:"}
- </label>
- <Textarea
- value={requestComment}
- onChange={(e) => setRequestComment(e.target.value)}
- placeholder={pendingComments.length > 0
- ? "Add any additional notes..."
- : "Please correct item #1, etc..."}
- className="min-h-[100px]"
- />
- </div>
-
- <DialogFooter>
- <Button
- variant="outline"
- onClick={() => setShowRequestDialog(false)}
- disabled={isLoading}
- >
- Cancel
- </Button>
- <Button
- onClick={handleSubmitRequestChanges}
- disabled={isLoading || (pendingComments.length === 0 && !requestComment.trim())}
- >
- Submit Changes
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
-
- {/* 승인 확인 다이얼로그 */}
- <Dialog open={showApproveDialog} onOpenChange={setShowApproveDialog}>
- <DialogContent>
- <DialogHeader>
- <DialogTitle>Confirm Approval</DialogTitle>
- <DialogDescription>
- Are you sure you want to approve this {pqType === 'project' ? 'project' : 'vendor'} PQ? You can add a comment if needed.
- </DialogDescription>
- </DialogHeader>
-
- <div className="space-y-2">
- <Textarea
- value={approveComment}
- onChange={(e) => setApproveComment(e.target.value)}
- placeholder="Optional: Add any comments about this approval"
- className="min-h-[100px]"
- />
- </div>
-
- <DialogFooter>
- <Button
- variant="outline"
- onClick={() => setShowApproveDialog(false)}
- disabled={isLoading}
- >
- Cancel
- </Button>
- <Button
- onClick={handleSubmitApprove}
- disabled={isLoading}
- >
- Confirm Approval
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
-
- {/* 거부 확인 다이얼로그 */}
- <Dialog open={showRejectDialog} onOpenChange={setShowRejectDialog}>
- <DialogContent>
- <DialogHeader>
- <DialogTitle>Confirm Rejection</DialogTitle>
- <DialogDescription>
- Are you sure you want to reject this {pqType === 'project' ? 'project' : 'vendor'} PQ? Please provide a reason.
- </DialogDescription>
- </DialogHeader>
-
- <div className="space-y-2">
- <Textarea
- value={rejectComment}
- onChange={(e) => setRejectComment(e.target.value)}
- placeholder="Required: Provide reason for rejection"
- className="min-h-[150px]"
- />
- </div>
-
- <DialogFooter>
- <Button
- variant="outline"
- onClick={() => setShowRejectDialog(false)}
- disabled={isLoading}
- >
- Cancel
- </Button>
- <Button
- onClick={handleSubmitReject}
- disabled={isLoading || !rejectComment.trim()}
- variant="destructive"
- >
- Confirm Rejection
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- </div>
- )
-}
-
-// 코멘트 추가 함수 인터페이스
-interface VendorPQReviewPageIntegratedProps {
- data: PQGroupData[];
- onCommentAdded: (comment: PendingComment) => void;
-}
-
-// 통합된 VendorPQReviewPage 컴포넌트
-function VendorPQReviewPageIntegrated({ data, onCommentAdded }: VendorPQReviewPageIntegratedProps) {
- 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 (
- <div className="space-y-4">
- {data.map((group) => (
- <Collapsible key={group.groupName} defaultOpen>
- <CollapsibleTrigger asChild>
- <div className="flex items-center justify-between cursor-pointer p-3 bg-muted rounded">
- <h2 className="font-semibold text-lg">{group.groupName}</h2>
- <Button variant="ghost" size="sm" className="p-0 h-7 w-7">
- <ChevronsUpDown className="h-4 w-4" />
- </Button>
- </div>
- </CollapsibleTrigger>
-
- <CollapsibleContent>
- <Card className="mt-2 p-4">
- <Table>
- <TableHeader>
- <TableRow>
- <TableHead className="w-[60px]">Code</TableHead>
- <TableHead>Check Point</TableHead>
- <TableHead>Answer</TableHead>
- <TableHead className="w-[180px]">Attachments</TableHead>
- <TableHead className="w-[60px] text-center">Comments</TableHead>
- </TableRow>
- </TableHeader>
-
- <TableBody>
- {group.items.map((item) => (
- <TableRow key={item.criteriaId}>
- <TableCell className="font-medium">{item.code}</TableCell>
- <TableCell>{item.checkPoint}</TableCell>
-
- <TableCell>
- {item.answer ? (
- <p className="whitespace-pre-wrap text-sm">
- {item.answer}
- </p>
- ) : (
- <p className="text-sm text-muted-foreground">(no answer)</p>
- )}
- </TableCell>
-
- <TableCell>
- {item.attachments.length > 0 ? (
- <ul className="list-none space-y-1">
- {item.attachments.map((file) => (
- <li key={file.attachId} className="text-sm flex items-center">
- <button
- className="text-blue-600 hover:text-blue-800 hover:underline flex items-center truncate max-w-[160px]"
- onClick={() => handleFileDownload(file.filePath, file.fileName)}
- >
- <Download className="h-3 w-3 mr-1 flex-shrink-0" />
- <span className="truncate">{file.fileName}</span>
- </button>
- </li>
- ))}
- </ul>
- ) : (
- <p className="text-sm text-muted-foreground">(none)</p>
- )}
- </TableCell>
-
- <TableCell className="text-center">
- <ItemCommentButton
- item={item}
- onCommentAdded={onCommentAdded}
- />
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- </Table>
- </Card>
- </CollapsibleContent>
- </Collapsible>
- ))}
- </div>
- );
-}
-
-// 항목 코멘트 버튼 컴포넌트 props
-interface ItemCommentButtonProps {
- item: any; // 항목 데이터
- onCommentAdded: (comment: PendingComment) => void;
-}
-
-// 항목별 코멘트 버튼 컴포넌트 (기존 로그 표시 + 메모리에 새 코멘트 저장)
-function ItemCommentButton({ item, onCommentAdded }: ItemCommentButtonProps) {
- const { toast } = useToast();
- const [open, setOpen] = React.useState(false);
- const [logs, setLogs] = React.useState<ReviewLog[]>([]);
- const [newComment, setNewComment] = React.useState("");
- const [isLoading, setIsLoading] = React.useState(false);
- const [hasComments, setHasComments] = React.useState(false);
-
- // If there's no answerId, item wasn't answered
- if (!item.answerId) {
- return <p className="text-xs text-muted-foreground">N/A</p>;
- }
-
- // 기존 로그 가져오기
- const fetchLogs = React.useCallback(async () => {
- try {
- setIsLoading(true);
- const res = await getItemReviewLogsAction({ answerId: item.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);
- }
- }, [item.answerId, toast]);
-
- // 초기 로드 시 코멘트 존재 여부 확인 (아이콘 색상용)
- React.useEffect(() => {
- const checkComments = async () => {
- try {
- const res = await getItemReviewLogsAction({ answerId: item.answerId });
- if (res.ok && res.data) {
- setHasComments(res.data.length > 0);
- }
- } catch (error) {
- console.error("Error checking comments:", error);
- }
- };
-
- checkComments();
- }, [item.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(() => {
- if (!newComment.trim()) return;
-
- setIsLoading(true);
-
- // 새 코멘트 생성
- const pendingComment: PendingComment = {
- answerId: item.answerId,
- checkPoint: item.checkPoint,
- code: item.code,
- comment: newComment.trim(),
- createdAt: new Date()
- };
-
- // 부모 컴포넌트에 전달
- onCommentAdded(pendingComment);
-
- // 상태 초기화
- setNewComment("");
- setOpen(false);
- setIsLoading(false);
- }, [item, newComment, onCommentAdded]);
-
- return (
- <>
- <Button variant="ghost" size="sm" onClick={handleButtonClick}>
- <MessagesSquare
- className={`h-4 w-4 ${hasComments ? 'text-blue-600' : ''}`}
- />
- </Button>
-
- <Dialog open={open} onOpenChange={handleOpenChange}>
- <DialogContent>
- <DialogHeader>
- <DialogTitle>{item.checkPoint}</DialogTitle>
- <DialogDescription>
- Review existing comments and add new ones
- </DialogDescription>
- </DialogHeader>
-
- {/* 기존 로그 섹션 */}
- <div className="max-h-[200px] overflow-y-auto space-y-2">
- {isLoading ? (
- <div className="flex justify-center p-4">
- <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
- </div>
- ) : logs.length > 0 ? (
- <div className="space-y-2">
- <h3 className="text-sm font-medium">Previous Comments:</h3>
- {logs.map((log) => (
- <div key={log.id} className="p-2 border rounded text-sm">
- <p className="font-medium">{log.reviewerName}</p>
- <p>{log.reviewerComment}</p>
- <p className="text-xs text-muted-foreground">
- {formatDate(log.createdAt, "KR")}
- </p>
- </div>
- ))}
- </div>
- ) : (
- <p className="text-sm text-muted-foreground">No previous comments yet.</p>
- )}
- </div>
-
- {/* 구분선 */}
- {/* <Separator /> */}
-
- {/* 새 코멘트 추가 섹션 */}
- <div className="space-y-2 mt-2">
- <div className="flex items-center justify-between">
- {/* <h3 className="text-sm font-medium">Add New Comment:</h3> */}
- {/* <p className="text-xs text-muted-foreground">
- Comments will be saved when you click "Request Changes"
- </p> */}
- </div>
- <Textarea
- placeholder="Add your comment..."
- value={newComment}
- onChange={(e) => setNewComment(e.target.value)}
- className="min-h-[100px]"
- />
- <Button
- onClick={handleAddComment}
- disabled={isLoading || !newComment.trim()}
- >
- Add Comment
- </Button>
- </div>
- </DialogContent>
- </Dialog>
- </>
- );
-} \ No newline at end of file