summaryrefslogtreecommitdiff
path: root/lib/bidding/list/bidding-detail-dialogs.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-08-13 11:05:09 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-08-13 11:05:09 +0000
commit33be47506f0aa62b969d82521580a29e95080268 (patch)
tree6b7e232f2d78ef8775944ea085a36b3ccbce7d95 /lib/bidding/list/bidding-detail-dialogs.tsx
parent2ac95090157c355ea1bd0b8eb1e1e5e2bd56faf4 (diff)
(대표님) 입찰, 법무검토, EDP 변경사항 대응, dolce 개선, form-data 개선, 정규업체 등록관리 추가
(최겸) pq 미사용 컴포넌트 및 페이지 제거, 파일 라우트에 pq 적용
Diffstat (limited to 'lib/bidding/list/bidding-detail-dialogs.tsx')
-rw-r--r--lib/bidding/list/bidding-detail-dialogs.tsx754
1 files changed, 754 insertions, 0 deletions
diff --git a/lib/bidding/list/bidding-detail-dialogs.tsx b/lib/bidding/list/bidding-detail-dialogs.tsx
new file mode 100644
index 00000000..2e58d676
--- /dev/null
+++ b/lib/bidding/list/bidding-detail-dialogs.tsx
@@ -0,0 +1,754 @@
+"use client"
+
+import * as React from "react"
+import { useState, useEffect } from "react"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Separator } from "@/components/ui/separator"
+import { ScrollArea } from "@/components/ui/scroll-area"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip"
+import {
+ CalendarIcon,
+ ClockIcon,
+ MapPinIcon,
+ FileTextIcon,
+ DownloadIcon,
+ EyeIcon,
+ PackageIcon,
+ HashIcon,
+ DollarSignIcon,
+ WeightIcon,
+ ExternalLinkIcon
+} from "lucide-react"
+import { toast } from "sonner"
+import { BiddingListItem } from "@/db/schema"
+import { downloadFile, formatFileSize, getFileInfo } from "@/lib/file-download"
+import { getPRDetailsAction, getSpecificationMeetingDetailsAction } from "../service"
+
+// 타입 정의
+interface SpecificationMeetingDetails {
+ id: number;
+ biddingId: number;
+ meetingDate: string;
+ meetingTime: string | null;
+ location: string | null;
+ address: string | null;
+ contactPerson: string | null;
+ contactPhone: string | null;
+ contactEmail: string | null;
+ agenda: string | null;
+ materials: string | null;
+ notes: string | null;
+ isRequired: boolean;
+ createdAt: string;
+ updatedAt: string;
+ documents: Array<{
+ id: number;
+ fileName: string;
+ originalFileName: string;
+ fileSize: number;
+ filePath: string;
+ title: string | null;
+ uploadedAt: string;
+ uploadedBy: string | null;
+ }>;
+}
+
+interface PRDetails {
+ documents: Array<{
+ id: number;
+ documentName: string;
+ fileName: string;
+ originalFileName: string;
+ fileSize: number;
+ filePath: string;
+ registeredAt: string;
+ registeredBy: string | null;
+ version: string | null;
+ description: string | null;
+ createdAt: string;
+ updatedAt: string;
+ }>;
+ items: Array<{
+ id: number;
+ itemNumber: string;
+ itemInfo: string | null;
+ quantity: number | null;
+ quantityUnit: string | null;
+ requestedDeliveryDate: string | null;
+ prNumber: string | null;
+ annualUnitPrice: number | null;
+ currency: string | null;
+ totalWeight: number | null;
+ weightUnit: string | null;
+ materialDescription: string | null;
+ hasSpecDocument: boolean;
+ createdAt: string;
+ updatedAt: string;
+ specDocuments: Array<{
+ id: number;
+ fileName: string;
+ originalFileName: string;
+ fileSize: number;
+ filePath: string;
+ uploadedAt: string;
+ title: string | null;
+ }>;
+ }>;
+}
+
+interface ActionResult<T> {
+ success: boolean;
+ data?: T;
+ error?: string;
+}
+
+// 파일 다운로드 훅
+const useFileDownload = () => {
+ const [downloadingFiles, setDownloadingFiles] = useState<Set<string>>(new Set());
+
+ const handleDownload = async (filePath: string, fileName: string, options?: {
+ action?: 'download' | 'preview'
+ }) => {
+ const fileKey = `${filePath}_${fileName}`;
+ if (downloadingFiles.has(fileKey)) return;
+
+ setDownloadingFiles(prev => new Set(prev).add(fileKey));
+
+ try {
+ await downloadFile(filePath, fileName, {
+ action: options?.action || 'download',
+ showToast: true,
+ showSuccessToast: true,
+ onError: (error) => {
+ console.error("파일 다운로드 실패:", error);
+ },
+ onSuccess: (fileName, fileSize) => {
+ console.log("파일 다운로드 성공:", fileName, fileSize ? formatFileSize(fileSize) : '');
+ }
+ });
+ } catch (error) {
+ console.error("다운로드 처리 중 오류:", error);
+ } finally {
+ setDownloadingFiles(prev => {
+ const newSet = new Set(prev);
+ newSet.delete(fileKey);
+ return newSet;
+ });
+ }
+ };
+
+ return { handleDownload, downloadingFiles };
+};
+
+// 파일 링크 컴포넌트
+interface FileDownloadLinkProps {
+ filePath: string;
+ fileName: string;
+ fileSize?: number;
+ title?: string | null;
+ className?: string;
+}
+
+const FileDownloadLink: React.FC<FileDownloadLinkProps> = ({
+ filePath,
+ fileName,
+ fileSize,
+ title,
+ className = ""
+}) => {
+ const { handleDownload, downloadingFiles } = useFileDownload();
+ const fileInfo = getFileInfo(fileName);
+ const fileKey = `${filePath}_${fileName}`;
+ const isDownloading = downloadingFiles.has(fileKey);
+
+ return (
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <button
+ onClick={() => handleDownload(filePath, fileName)}
+ disabled={isDownloading}
+ className={`inline-flex items-center gap-1 text-sm text-blue-600 hover:text-blue-800 hover:underline disabled:opacity-50 disabled:cursor-not-allowed ${className}`}
+ >
+ <span className="text-xs">{fileInfo.icon}</span>
+ <span className="truncate max-w-[150px]">
+ {isDownloading ? "다운로드 중..." : (title || fileName)}
+ </span>
+ <ExternalLinkIcon className="h-3 w-3 opacity-60" />
+ </button>
+ </TooltipTrigger>
+ <TooltipContent>
+ <div className="text-xs">
+ <div className="font-medium">{fileName}</div>
+ {fileSize && <div className="text-muted-foreground">{formatFileSize(fileSize)}</div>}
+ <div className="text-muted-foreground">클릭하여 다운로드</div>
+ </div>
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ );
+};
+
+// 파일 다운로드 버튼 컴포넌트 (간소화된 버전)
+interface FileDownloadButtonProps {
+ filePath: string;
+ fileName: string;
+ fileSize?: number;
+ title?: string | null;
+ variant?: "download" | "preview";
+ size?: "sm" | "default" | "lg";
+}
+
+const FileDownloadButton: React.FC<FileDownloadButtonProps> = ({
+ filePath,
+ fileName,
+ fileSize,
+ title,
+ variant = "download",
+ size = "sm"
+}) => {
+ const { handleDownload, downloadingFiles } = useFileDownload();
+ const fileInfo = getFileInfo(fileName);
+ const fileKey = `${filePath}_${fileName}`;
+ const isDownloading = downloadingFiles.has(fileKey);
+
+ const Icon = variant === "preview" && fileInfo.canPreview ? EyeIcon : DownloadIcon;
+
+ return (
+ <Button
+ onClick={() => handleDownload(filePath, fileName, { action: variant })}
+ disabled={isDownloading}
+ size={size}
+ variant="outline"
+ className="gap-2"
+ >
+ <Icon className="h-4 w-4" />
+ {isDownloading ? "처리중..." : (
+ variant === "preview" && fileInfo.canPreview ? "미리보기" : "다운로드"
+ )}
+ {fileSize && size !== "sm" && (
+ <span className="text-xs text-muted-foreground">
+ ({formatFileSize(fileSize)})
+ </span>
+ )}
+ </Button>
+ );
+};
+
+// 사양설명회 다이얼로그
+interface SpecificationMeetingDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ bidding: BiddingListItem | null;
+}
+
+export function SpecificationMeetingDialog({
+ open,
+ onOpenChange,
+ bidding
+}: SpecificationMeetingDialogProps) {
+ const [data, setData] = useState<SpecificationMeetingDetails | null>(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState<string | null>(null);
+
+ useEffect(() => {
+ if (open && bidding) {
+ fetchSpecificationMeetingData();
+ }
+ }, [open, bidding]);
+
+ const fetchSpecificationMeetingData = async () => {
+ if (!bidding) return;
+
+ setLoading(true);
+ setError(null);
+
+ try {
+ const result = await getSpecificationMeetingDetailsAction(bidding.id);
+
+ if (result.success && result.data) {
+ setData(result.data);
+ } else {
+ setError(result.error || "사양설명회 정보를 불러올 수 없습니다.");
+ }
+ } catch (err) {
+ setError("데이터 로딩 중 오류가 발생했습니다.");
+ console.error("Failed to fetch specification meeting data:", err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const formatDate = (dateString: string) => {
+ try {
+ return new Date(dateString).toLocaleDateString('ko-KR', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ weekday: 'long'
+ });
+ } catch {
+ return dateString;
+ }
+ };
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-4xl max-h-[90vh]">
+ <DialogHeader>
+ <DialogTitle className="flex items-center gap-2">
+ <CalendarIcon className="h-5 w-5" />
+ 사양설명회 정보
+ </DialogTitle>
+ <DialogDescription>
+ {bidding?.title}의 사양설명회 상세 정보입니다.
+ </DialogDescription>
+ </DialogHeader>
+
+ <ScrollArea className="max-h-[75vh]">
+ {loading ? (
+ <div className="flex items-center justify-center py-6">
+ <div className="text-center">
+ <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary mx-auto mb-2"></div>
+ <p className="text-sm text-muted-foreground">로딩 중...</p>
+ </div>
+ </div>
+ ) : error ? (
+ <div className="flex items-center justify-center py-6">
+ <div className="text-center">
+ <p className="text-sm text-destructive mb-2">{error}</p>
+ <Button onClick={fetchSpecificationMeetingData} size="sm">
+ 다시 시도
+ </Button>
+ </div>
+ </div>
+ ) : data ? (
+ <div className="space-y-4">
+ {/* 기본 정보 */}
+ <Card>
+ <CardHeader className="pb-3">
+ <CardTitle className="text-base">기본 정보</CardTitle>
+ </CardHeader>
+ <CardContent className="pt-0">
+ <div className="text-sm space-y-1">
+ <div>
+ <CalendarIcon className="inline h-3 w-3 text-muted-foreground mr-2" />
+ <span className="font-medium">날짜:</span> {formatDate(data.meetingDate)}
+ {data.meetingTime && <span className="ml-4"><ClockIcon className="inline h-3 w-3 text-muted-foreground mr-1" />{data.meetingTime}</span>}
+ </div>
+
+ {data.location && (
+ <div>
+ <MapPinIcon className="inline h-3 w-3 text-muted-foreground mr-2" />
+ <span className="font-medium">장소:</span> {data.location}
+ {data.address && <span className="text-muted-foreground ml-2">({data.address})</span>}
+ </div>
+ )}
+
+ <div>
+ <span className="font-medium">참석 필수:</span>
+ <Badge variant={data.isRequired ? "destructive" : "secondary"} className="text-xs ml-2">
+ {data.isRequired ? "필수" : "선택"}
+ </Badge>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+
+ {/* 연락처 정보 */}
+ {(data.contactPerson || data.contactPhone || data.contactEmail) && (
+ <Card>
+ <CardHeader className="pb-3">
+ <CardTitle className="text-base">연락처 정보</CardTitle>
+ </CardHeader>
+ <CardContent className="pt-0">
+ <div className="text-sm">
+ {[
+ data.contactPerson && `담당자: ${data.contactPerson}`,
+ data.contactPhone && `전화: ${data.contactPhone}`,
+ data.contactEmail && `이메일: ${data.contactEmail}`
+ ].filter(Boolean).join(' • ')}
+ </div>
+ </CardContent>
+ </Card>
+ )}
+
+ {/* 안건 및 준비물 */}
+ {(data.agenda || data.materials) && (
+ <Card>
+ <CardHeader className="pb-3">
+ <CardTitle className="text-base">안건 및 준비물</CardTitle>
+ </CardHeader>
+ <CardContent className="pt-0 space-y-3">
+ {data.agenda && (
+ <div>
+ <span className="font-medium text-sm">안건:</span>
+ <div className="mt-1 p-2 bg-muted rounded text-sm whitespace-pre-wrap">
+ {data.agenda}
+ </div>
+ </div>
+ )}
+
+ {data.materials && (
+ <div>
+ <span className="font-medium text-sm">준비물:</span>
+ <div className="mt-1 p-2 bg-muted rounded text-sm whitespace-pre-wrap">
+ {data.materials}
+ </div>
+ </div>
+ )}
+ </CardContent>
+ </Card>
+ )}
+
+ {/* 비고 */}
+ {data.notes && (
+ <Card>
+ <CardHeader className="pb-3">
+ <CardTitle className="text-base">비고</CardTitle>
+ </CardHeader>
+ <CardContent className="pt-0">
+ <div className="p-2 bg-muted rounded text-sm whitespace-pre-wrap">
+ {data.notes}
+ </div>
+ </CardContent>
+ </Card>
+ )}
+
+ {/* 관련 문서 */}
+ {data.documents.length > 0 && (
+ <Card>
+ <CardHeader className="pb-3">
+ <CardTitle className="text-base flex items-center gap-2">
+ <FileTextIcon className="h-4 w-4" />
+ 관련 문서 ({data.documents.length}개)
+ </CardTitle>
+ </CardHeader>
+ <CardContent className="pt-0">
+ <div className="space-y-2">
+ {data.documents.map((doc) => (
+ <div key={doc.id} className="flex items-center gap-2">
+ <FileDownloadLink
+ filePath={doc.filePath}
+ fileName={doc.originalFileName}
+ fileSize={doc.fileSize}
+ title={doc.title}
+ />
+ </div>
+ ))}
+ </div>
+ </CardContent>
+ </Card>
+ )}
+ </div>
+ ) : null}
+ </ScrollArea>
+ </DialogContent>
+ </Dialog>
+ );
+}
+
+// PR 문서 다이얼로그
+interface PrDocumentsDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ bidding: BiddingListItem | null;
+}
+
+export function PrDocumentsDialog({
+ open,
+ onOpenChange,
+ bidding
+}: PrDocumentsDialogProps) {
+ const [data, setData] = useState<PRDetails | null>(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState<string | null>(null);
+
+ useEffect(() => {
+ if (open && bidding) {
+ fetchPRData();
+ }
+ }, [open, bidding]);
+
+ const fetchPRData = async () => {
+ if (!bidding) return;
+
+ setLoading(true);
+ setError(null);
+
+ try {
+ const result = await getPRDetailsAction(bidding.id);
+
+ if (result.success && result.data) {
+ setData(result.data);
+ } else {
+ setError(result.error || "PR 문서 정보를 불러올 수 없습니다.");
+ }
+ } catch (err) {
+ setError("데이터 로딩 중 오류가 발생했습니다.");
+ console.error("Failed to fetch PR data:", err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const formatCurrency = (amount: number | null, currency: string | null) => {
+ if (amount === null) return "-";
+ return `${amount.toLocaleString()} ${currency || ""}`;
+ };
+
+ const formatWeight = (weight: number | null, unit: string | null) => {
+ if (weight === null) return "-";
+ return `${weight.toLocaleString()} ${unit || ""}`;
+ };
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-7xl max-h-[90vh]">
+ <DialogHeader>
+ <DialogTitle className="flex items-center gap-2">
+ <PackageIcon className="h-5 w-5" />
+ PR 문서
+ </DialogTitle>
+ <DialogDescription>
+ {bidding?.title}의 PR 문서 및 아이템 정보입니다.
+ </DialogDescription>
+ </DialogHeader>
+
+ <ScrollArea className="max-h-[75vh]">
+ {loading ? (
+ <div className="flex items-center justify-center py-8">
+ <div className="text-center">
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-2"></div>
+ <p className="text-sm text-muted-foreground">로딩 중...</p>
+ </div>
+ </div>
+ ) : error ? (
+ <div className="flex items-center justify-center py-8">
+ <div className="text-center">
+ <p className="text-sm text-destructive mb-2">{error}</p>
+ <Button onClick={fetchPRData} size="sm">
+ 다시 시도
+ </Button>
+ </div>
+ </div>
+ ) : data ? (
+ <div className="space-y-6">
+ {/* PR 문서 목록 */}
+ {data.documents.length > 0 && (
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg flex items-center gap-2">
+ <FileTextIcon className="h-5 w-5" />
+ PR 문서 ({data.documents.length}개)
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ <Table>
+ <TableHeader>
+ <TableRow>
+ <TableHead>문서명</TableHead>
+ <TableHead>파일명</TableHead>
+ <TableHead>버전</TableHead>
+ <TableHead>크기</TableHead>
+ <TableHead>등록일</TableHead>
+ <TableHead>등록자</TableHead>
+ <TableHead className="text-right">다운로드</TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {data.documents.map((doc) => (
+ <TableRow key={doc.id}>
+ <TableCell className="font-medium">
+ {doc.documentName}
+ {doc.description && (
+ <div className="text-xs text-muted-foreground mt-1">
+ {doc.description}
+ </div>
+ )}
+ </TableCell>
+ <TableCell>
+ <FileDownloadLink
+ filePath={doc.filePath}
+ fileName={doc.originalFileName}
+ fileSize={doc.fileSize}
+ />
+ </TableCell>
+ <TableCell>
+ {doc.version ? (
+ <Badge variant="outline">{doc.version}</Badge>
+ ) : "-"}
+ </TableCell>
+ <TableCell>{formatFileSize(doc.fileSize)}</TableCell>
+ <TableCell>
+ {new Date(doc.registeredAt).toLocaleDateString('ko-KR')}
+ </TableCell>
+ <TableCell>{doc.registeredBy || "-"}</TableCell>
+ <TableCell className="text-right">
+ <FileDownloadButton
+ filePath={doc.filePath}
+ fileName={doc.originalFileName}
+ fileSize={doc.fileSize}
+ variant="download"
+ size="sm"
+ />
+ </TableCell>
+ </TableRow>
+ ))}
+ </TableBody>
+ </Table>
+ </CardContent>
+ </Card>
+ )}
+
+ {/* PR 아이템 테이블 */}
+ {data.items.length > 0 && (
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg flex items-center gap-2">
+ <HashIcon className="h-5 w-5" />
+ PR 아이템 ({data.items.length}개)
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ <Table>
+ <TableHeader>
+ <TableRow>
+ <TableHead className="w-[100px]">아이템 번호</TableHead>
+ <TableHead className="w-[150px]">PR 번호</TableHead>
+ <TableHead>아이템 정보</TableHead>
+ <TableHead className="w-[120px]">수량</TableHead>
+ <TableHead className="w-[120px]">단가</TableHead>
+ <TableHead className="w-[120px]">중량</TableHead>
+ <TableHead className="w-[120px]">요청 납기</TableHead>
+ <TableHead className="w-[200px]">스펙 문서</TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {data.items.map((item) => (
+ <TableRow key={item.id}>
+ <TableCell className="font-medium">
+ {item.itemNumber}
+ </TableCell>
+ <TableCell>
+ {item.prNumber || "-"}
+ </TableCell>
+ <TableCell>
+ <div>
+ {item.itemInfo && (
+ <div className="font-medium text-sm mb-1">{item.itemInfo}</div>
+ )}
+ {item.materialDescription && (
+ <div className="text-xs text-muted-foreground">
+ {item.materialDescription}
+ </div>
+ )}
+ </div>
+ </TableCell>
+ <TableCell>
+ <div className="flex items-center gap-1">
+ <PackageIcon className="h-3 w-3 text-muted-foreground" />
+ <span className="text-sm">
+ {item.quantity ? `${item.quantity.toLocaleString()} ${item.quantityUnit || ""}` : "-"}
+ </span>
+ </div>
+ </TableCell>
+ <TableCell>
+ <div className="flex items-center gap-1">
+ <DollarSignIcon className="h-3 w-3 text-muted-foreground" />
+ <span className="text-sm">
+ {formatCurrency(item.annualUnitPrice, item.currency)}
+ </span>
+ </div>
+ </TableCell>
+ <TableCell>
+ <div className="flex items-center gap-1">
+ <WeightIcon className="h-3 w-3 text-muted-foreground" />
+ <span className="text-sm">
+ {formatWeight(item.totalWeight, item.weightUnit)}
+ </span>
+ </div>
+ </TableCell>
+ <TableCell>
+ {item.requestedDeliveryDate ? (
+ <div className="flex items-center gap-1">
+ <CalendarIcon className="h-3 w-3 text-muted-foreground" />
+ <span className="text-sm">
+ {new Date(item.requestedDeliveryDate).toLocaleDateString('ko-KR')}
+ </span>
+ </div>
+ ) : "-"}
+ </TableCell>
+ <TableCell>
+ <div className="space-y-1">
+ <div className="flex items-center gap-2">
+ <Badge variant={item.hasSpecDocument ? "default" : "secondary"} className="text-xs">
+ {item.hasSpecDocument ? "있음" : "없음"}
+ </Badge>
+ {item.specDocuments.length > 0 && (
+ <span className="text-xs text-muted-foreground">
+ ({item.specDocuments.length}개)
+ </span>
+ )}
+ </div>
+ {item.specDocuments.length > 0 && (
+ <div className="space-y-1">
+ {item.specDocuments.map((doc, index) => (
+ <div key={doc.id} className="text-xs">
+ <FileDownloadLink
+ filePath={doc.filePath}
+ fileName={doc.originalFileName}
+ fileSize={doc.fileSize}
+ title={doc.title}
+ className="text-xs"
+ />
+ </div>
+ ))}
+ </div>
+ )}
+ </div>
+ </TableCell>
+ </TableRow>
+ ))}
+ </TableBody>
+ </Table>
+ </CardContent>
+ </Card>
+ )}
+
+ {/* 데이터가 없는 경우 */}
+ {data.documents.length === 0 && data.items.length === 0 && (
+ <div className="text-center py-8">
+ <FileTextIcon className="h-12 w-12 text-muted-foreground mx-auto mb-2" />
+ <p className="text-muted-foreground">PR 문서가 없습니다.</p>
+ </div>
+ )}
+ </div>
+ ) : null}
+ </ScrollArea>
+ </DialogContent>
+ </Dialog>
+ );
+} \ No newline at end of file