From 02b1cf005cf3e1df64183d20ba42930eb2767a9f Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Thu, 21 Aug 2025 06:57:36 +0000
Subject: (대표님, 최겸) 설계메뉴추가, 작업사항 업데이트 설계메뉴 - 문서관리
 설계메뉴 - 벤더 데이터 gtc 메뉴 업데이트 정보시스템 - 메뉴리스트 및 정보
 업데이트 파일 라우트 업데이트 엑셀임포트 개선 기본계약 개선 벤더 가입과정
 변경 및 개선 벤더 기본정보 - pq 돌체 오류 수정 및 개선 벤더 로그인 과정
 이메일 오류 수정
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../user-vendor-document-table-container.tsx       | 898 +++++++++++++++++++++
 1 file changed, 898 insertions(+)
 create mode 100644 components/ship-vendor-document-all/user-vendor-document-table-container.tsx

(limited to 'components/ship-vendor-document-all/user-vendor-document-table-container.tsx')

diff --git a/components/ship-vendor-document-all/user-vendor-document-table-container.tsx b/components/ship-vendor-document-all/user-vendor-document-table-container.tsx
new file mode 100644
index 00000000..157bdb03
--- /dev/null
+++ b/components/ship-vendor-document-all/user-vendor-document-table-container.tsx
@@ -0,0 +1,898 @@
+// user-vendor-document-display.tsx - 수정된 버전
+"use client"
+
+import React from "react"
+import {
+  Card,
+  CardContent,
+  CardDescription,
+  CardHeader,
+  CardTitle,
+} from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import {
+  Table,
+  TableBody,
+  TableCaption,
+  TableCell,
+  TableHead,
+  TableHeader,
+  TableRow,
+} from "@/components/ui/table"
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogHeader,
+  DialogTitle,
+} from "@/components/ui/dialog"
+import { Building, FileText, AlertCircle, Eye, Download, Loader2, Plus, Trash2, Edit } from "lucide-react"
+import { SimplifiedDocumentsTable } from "@/lib/vendor-document-list/ship-all/enhanced-documents-table"
+import {
+  getUserVendorDocumentsAll,
+  getUserVendorDocumentStatsAll,
+} from "@/lib/vendor-document-list/enhanced-document-service"
+import { SimplifiedDocumentsView } from "@/db/schema"
+import { WebViewerInstance } from "@pdftron/webviewer"
+import { useRouter } from 'next/navigation'
+
+/* -------------------------------------------------------------------------------------------------
+ * Types & Constants
+ * -----------------------------------------------------------------------------------------------*/
+interface UserVendorDocumentDisplayProps {
+  allPromises: Promise<[
+    Awaited<ReturnType<typeof getUserVendorDocumentsAll>>, // 문서 목록
+    Awaited<ReturnType<typeof getUserVendorDocumentStatsAll>>, // 통계 데이터
+  ]>
+}
+
+interface StageInfo {
+  id: number
+  stageName: string
+  stageStatus: string
+  stageOrder: number
+  planDate: string | null
+  actualDate: string | null
+  assigneeName: string | null
+  priority: string
+  revisions: RevisionInfo[]
+}
+
+interface RevisionInfo {
+  id: number
+  serialNo: string | null
+  issueStageId: number
+  revision: string
+  uploaderType: string
+  uploaderId: number | null
+  uploaderName: string | null
+  comment: string | null
+  usage: string | null
+  usageType: string | null
+  revisionStatus: string
+  submittedDate: string | null
+  approvedDate: string | null
+  uploadedAt: string | null
+  reviewStartDate: string | null
+  rejectedDate: string | null
+  reviewerId: number | null
+  reviewerName: string | null
+  reviewComments: string | null
+  createdAt: Date
+  updatedAt: Date
+  stageName?: string
+  attachments: AttachmentInfo[]
+}
+
+interface AttachmentInfo {
+  id: number
+  revisionId: number
+  fileName: string
+  filePath: string
+  dolceFilePath: string | null
+  fileSize: number | null
+  fileType: string | null
+  createdAt: Date
+  updatedAt: Date
+}
+
+interface DocumentSelectionContextType {
+  selectedDocumentId: number | null
+  selectedStageId: number | null
+  selectedRevisionId: number | null
+  setSelectedDocumentId: (id: number | null) => void
+  setSelectedStageId: (id: number | null) => void
+  setSelectedRevisionId: (id: number | null) => void
+  allData: SimplifiedDocumentsView[] | null
+  setAllData: (data: SimplifiedDocumentsView[]) => void
+}
+
+export const DocumentSelectionContextAll = React.createContext<DocumentSelectionContextType>(
+  {
+    selectedDocumentId: null,
+    selectedStageId: null,
+    selectedRevisionId: null,
+    setSelectedDocumentId: (_id: number | null) => { },
+    setSelectedStageId: (_id: number | null) => { },
+    setSelectedRevisionId: (_id: number | null) => { },
+    allData: null,
+    setAllData: (_data: SimplifiedDocumentsView[]) => { },
+  },
+)
+
+/* -------------------------------------------------------------------------------------------------
+ * Revision & Attachment Tables - 너비 최적화
+ * -----------------------------------------------------------------------------------------------*/
+
+function getUsageTypeDisplay(usageType: string | null): string {
+  if (!usageType) return '-'
+
+  // B3 용도 타입 축약 표시
+  const abbreviations: Record<string, string> = {
+    'Approval Submission Full': 'AS-F',
+    'Approval Submission Partial': 'AS-P',
+    'Approval Completion Full': 'AC-F',
+    'Approval Completion Partial': 'AC-P',
+    'Working Full': 'W-F',
+    'Working Partial': 'W-P',
+    'Reference Full': 'R-F',
+    'Reference Partial': 'R-P',
+    'Reference Series Full': 'RS-F',
+    'Reference Series Partial': 'RS-P',
+  }
+
+  return abbreviations[usageType] || usageType
+}
+
+function RevisionTable({
+  revisions,
+  onViewRevision,
+}: {
+  revisions: RevisionInfo[]
+  onViewRevision: (revision: RevisionInfo) => void
+}) {
+  const { selectedRevisionId, setSelectedRevisionId } =
+    React.useContext(DocumentSelectionContextAll)
+
+  const toggleSelect = (revisionId: number) => {
+    setSelectedRevisionId(revisionId === selectedRevisionId ? null : revisionId)
+  }
+
+  const canEditRevision = React.useCallback((revision: RevisionInfo) => {
+    if ((!revision.attachments || revision.attachments.length === 0) && revision.uploaderType === "vendor") {
+      return true
+    }
+
+    return revision.attachments.every(attachment => 
+      !attachment.dolceFilePath || attachment.dolceFilePath.trim() === ''
+    )
+  }, [])
+
+  const getRevisionProcessStatus = React.useCallback((revision: RevisionInfo) => {
+    if (!revision.attachments || revision.attachments.length === 0) {
+      return 'no-files'
+    }
+
+    const processedCount = revision.attachments.filter(attachment => 
+      attachment.dolceFilePath && attachment.dolceFilePath.trim() !== ''
+    ).length
+
+    if (processedCount === 0) {
+      return 'not-processed'
+    } else if (processedCount === revision.attachments.length) {
+      return 'fully-processed'
+    } else {
+      return 'partially-processed'
+    }
+  }, [])
+
+  return (
+    <Card className="flex-1 min-w-0 max-w-full">
+      <CardHeader className="pb-3">
+        <CardTitle className="text-lg">Revisions</CardTitle>
+      </CardHeader>
+      <CardContent className="p-2 overflow-hidden">
+        <div className="w-full overflow-x-auto">
+          <Table className="w-full" style={{ tableLayout: 'fixed', minWidth: '800px' }}>
+            <TableHeader>
+              <TableRow>
+                <TableHead style={{ width: '40px' }}>Sel</TableHead>
+                <TableHead style={{ width: '60px' }}>Serial No</TableHead>
+                <TableHead style={{ width: '80px' }}>Rev</TableHead>
+                <TableHead style={{ width: '60px' }}>Category</TableHead>
+                <TableHead style={{ width: '80px' }}>Usage</TableHead>
+                <TableHead style={{ width: '80px' }}>Type</TableHead>
+                <TableHead style={{ width: '90px' }}>Status</TableHead>
+                <TableHead style={{ width: '100px' }}>Uploader</TableHead>
+                <TableHead style={{ width: '120px' }}>Comment</TableHead>
+                <TableHead style={{ width: '100px' }}>Date</TableHead>
+                <TableHead style={{ width: '60px' }} className="text-center">Files</TableHead>
+                <TableHead style={{ width: '80px' }}>Actions</TableHead>
+              </TableRow>
+            </TableHeader>
+            <TableBody>
+              {revisions.map((revision) => {
+                const canEdit = canEditRevision(revision)
+                const processStatus = getRevisionProcessStatus(revision)
+                
+                return (
+                  <TableRow
+                    key={revision.id}
+                    className={`revision-table-row ${selectedRevisionId === revision.id ? 'selected' : ''}`}
+                  >
+                    <TableCell style={{ width: '40px' }}>
+                      <input
+                        type="checkbox"
+                        checked={selectedRevisionId === revision.id}
+                        onChange={() => toggleSelect(revision.id)}
+                        className="h-3 w-3 cursor-pointer"
+                      />
+                    </TableCell>
+                    <TableCell style={{ width: '60px' }} className="font-mono font-medium">
+                      {revision.serialNo || ''}
+                    </TableCell>
+                    <TableCell style={{ width: '80px' }} className="font-mono font-medium">
+                      <div className="flex items-center gap-1">
+                        <span className="truncate text-xs">{revision.revision}</span>
+                        {processStatus === 'fully-processed' && (
+                          <div 
+                            className="w-1.5 h-1.5 bg-blue-500 rounded-full flex-shrink-0" 
+                            title="All files processed"
+                          />
+                        )}
+                        {processStatus === 'partially-processed' && (
+                          <div 
+                            className="w-1.5 h-1.5 bg-yellow-500 rounded-full flex-shrink-0" 
+                            title="Some files processed"
+                          />
+                        )}
+                      </div>
+                    </TableCell>
+                    <TableCell style={{ width: '60px' }} className="text-xs">
+                      {revision.uploaderType === "vendor" ? "To" : "From"}
+                    </TableCell>
+                    <TableCell style={{ width: '80px' }}>
+                      <span className="text-xs truncate block">
+                        {revision.usage || '-'}
+                      </span>
+                    </TableCell>
+                    <TableCell style={{ width: '80px' }}>
+                      <span className="text-xs truncate block">
+                        {revision.usageType ? (
+                          getUsageTypeDisplay(revision.usageType)
+                        ) : (
+                          <span className="text-gray-400">-</span>
+                        )}
+                      </span>
+                    </TableCell>
+                    <TableCell style={{ width: '90px' }}>
+                      <Badge
+                        variant={
+                          revision.revisionStatus === 'APPROVED'
+                            ? 'default'
+                            : 'secondary'
+                        }
+                        className="text-xs truncate max-w-full"
+                      >
+                        {revision.revisionStatus.slice(0, 8)}
+                      </Badge>
+                    </TableCell>
+                    <TableCell style={{ width: '100px' }}>
+                      <span className="text-xs truncate block">{revision.uploaderName || '-'}</span>
+                    </TableCell>
+                    <TableCell style={{ width: '120px' }}>
+                      {revision.comment ? (
+                        <div className="w-full">
+                          <p className="text-xs text-gray-700 bg-gray-50 p-1 rounded truncate" title={revision.comment}>
+                            {revision.comment}
+                          </p>
+                        </div>
+                      ) : (
+                        <span className="text-gray-400 text-xs">-</span>
+                      )}
+                    </TableCell>
+                    <TableCell style={{ width: '100px' }}>
+                      <span className="text-xs truncate block">
+                        {revision.uploadedAt
+                          ? new Date(revision.uploadedAt).toLocaleDateString('en-US', { 
+                              month: 'short', 
+                              day: 'numeric' 
+                            })
+                          : '-'}
+                      </span>
+                    </TableCell>
+                    <TableCell style={{ width: '60px' }} className="text-center">
+                      <div className="flex items-center justify-center">
+                        <span className="text-xs">{revision.attachments.length}</span>
+                      </div>
+                    </TableCell>
+                    <TableCell style={{ width: '80px' }}>
+                      <div className="flex items-center justify-center">
+                        {revision.attachments.length > 0 && (
+                          <Button
+                            variant="ghost"
+                            size="sm"
+                            onClick={() => onViewRevision(revision)}
+                            className="h-6 w-6 p-0"
+                            title="View attachments"
+                          >
+                            <Eye className="h-3 w-3" />
+                          </Button>
+                        )}
+                      </div>
+                    </TableCell>
+                  </TableRow>
+                )
+              })}
+            </TableBody>
+          </Table>
+        </div>
+      </CardContent>
+    </Card>
+  )
+}
+
+function AttachmentTable({
+  attachments,
+  onDownloadFile,
+}: {
+  attachments: AttachmentInfo[]
+  onDownloadFile: (attachment: AttachmentInfo) => void
+}) {
+  const { selectedRevisionId, allData, setAllData } = React.useContext(DocumentSelectionContextAll)
+  const [addAttachmentDialogOpen, setAddAttachmentDialogOpen] = React.useState(false)
+  const router = useRouter()
+
+  const selectedRevisionInfo = React.useMemo(() => {
+    if (!selectedRevisionId || !allData) return null
+
+    for (const doc of allData) {
+      if (doc.allStages) {
+        for (const stage of doc.allStages as StageInfo[]) {
+          const revision = stage.revisions.find(r => r.id === selectedRevisionId)
+          if (revision) return revision
+        }
+      }
+    }
+    return null
+  }, [selectedRevisionId, allData])
+
+  const handleAddAttachment = React.useCallback(() => {
+    if (selectedRevisionInfo) {
+      setAddAttachmentDialogOpen(true)
+    }
+  }, [selectedRevisionInfo])
+
+  const canDeleteFile = React.useCallback((attachment: AttachmentInfo) => {
+    return !attachment.dolceFilePath || attachment.dolceFilePath.trim() === ''
+  }, [])
+
+  const handleAttachmentUploadSuccess = React.useCallback((uploadResult?: any) => {
+    if (!selectedRevisionId || !allData || !uploadResult?.data) {
+      console.log('🔄 Full refresh')
+      router.refresh()
+      return
+    }
+
+    try {
+      const newAttachments: AttachmentInfo[] = uploadResult.data.uploadedFiles?.map((file: any) => ({
+        id: file.id,
+        revisionId: selectedRevisionId,
+        fileName: file.fileName,
+        filePath: file.filePath,
+        dolceFilePath: null,
+        fileSize: file.fileSize,
+        fileType: file.fileType || null,
+        createdAt: new Date(),
+        updatedAt: new Date(),
+      })) || []
+
+      const updatedData = allData.map(doc => {
+        const updatedDoc = { ...doc }
+
+        if (updatedDoc.allStages) {
+          const stages = [...updatedDoc.allStages as StageInfo[]]
+
+          for (const stage of stages) {
+            const revisionIndex = stage.revisions.findIndex(r => r.id === selectedRevisionId)
+            if (revisionIndex !== -1) {
+              stage.revisions[revisionIndex] = {
+                ...stage.revisions[revisionIndex],
+                attachments: [...stage.revisions[revisionIndex].attachments, ...newAttachments]
+              }
+              updatedDoc.allStages = stages
+              break
+            }
+          }
+        }
+
+        return updatedDoc
+      })
+
+      setAllData(updatedData)
+      console.log('✅ AttachmentTable update complete')
+
+      setTimeout(() => {
+        router.refresh()
+      }, 1500)
+
+    } catch (error) {
+      console.error('❌ AttachmentTable update failed:', error)
+      router.refresh()
+    }
+  }, [selectedRevisionId, allData, setAllData, router])
+
+  return (
+    <Card className="w-72 flex-shrink-0 max-w-full min-w-0">
+      <CardHeader className="pb-3">
+        <div className="flex items-center justify-between">
+          <CardTitle className="text-lg truncate">Attachments</CardTitle>
+          {selectedRevisionId && selectedRevisionInfo && (
+            <Button
+              onClick={handleAddAttachment}
+              size="sm"
+              variant="outline"
+              className="flex items-center gap-1 h-7 px-2 flex-shrink-0"
+            >
+              <Plus className="h-3 w-3" />
+              <span className="text-xs">Add</span>
+            </Button>
+          )}
+        </div>
+      </CardHeader>
+      <CardContent className="p-2 overflow-hidden">
+        <div className="w-full overflow-x-auto">
+          <Table className="w-full" style={{ tableLayout: 'fixed', minWidth: '280px' }}>
+            <TableHeader>
+              <TableRow>
+                <TableHead style={{ width: '200px' }}>File Name</TableHead>
+                <TableHead style={{ width: '80px' }}>Actions</TableHead>
+              </TableRow>
+            </TableHeader>
+            <TableBody>
+              {!selectedRevisionId || attachments.length === 0 ? (
+                <TableRow>
+                  <TableCell colSpan={2} className="h-24 text-center">
+                    <div className="flex flex-col items-center gap-2 text-muted-foreground">
+                      <FileText className="h-6 w-6" />
+                      <span className="text-xs">
+                        {!selectedRevisionId
+                          ? 'Please select a revision'
+                          : 'No attached files'}
+                      </span>
+                      {selectedRevisionId && selectedRevisionInfo && (
+                        <Button
+                          onClick={handleAddAttachment}
+                          size="sm"
+                          variant="outline"
+                          className="mt-2 h-7 px-2"
+                        >
+                          <Plus className="h-3 w-3 mr-1" />
+                          <span className="text-xs">Add First File</span>
+                        </Button>
+                      )}
+                    </div>
+                  </TableCell>
+                </TableRow>
+              ) : (
+                attachments.map((file) => (
+                  <TableRow key={file.id}>
+                    <TableCell style={{ width: '200px' }} className="font-medium">
+                      <div className="min-w-0">
+                        <div className="truncate text-xs" title={file.fileName}>
+                          {file.fileName}
+                        </div>
+                        <div className="flex items-center gap-2 text-xs text-muted-foreground">
+                          <span>
+                            {file.fileSize
+                              ? file.fileSize >= 1024 * 1024
+                                ? `${(file.fileSize / 1024 / 1024).toFixed(1)}MB`
+                                : `${(file.fileSize / 1024).toFixed(1)}KB`
+                              : '-'}
+                          </span>
+                          {file.dolceFilePath && file.dolceFilePath.trim() !== '' && (
+                            <span className="text-blue-600 font-medium">Processed</span>
+                          )}
+                        </div>
+                      </div>
+                    </TableCell>
+                    <TableCell style={{ width: '80px' }}>
+                      <div className="flex items-center justify-center">
+                        <Button
+                          variant="ghost"
+                          size="sm"
+                          onClick={() => onDownloadFile(file)}
+                          className="h-6 w-6 p-0"
+                          title="Download file"
+                        >
+                          <Download className="h-3 w-3" />
+                        </Button>
+                      </div>
+                    </TableCell>
+                  </TableRow>
+                ))
+              )}
+            </TableBody>
+          </Table>
+        </div>
+      </CardContent>
+    </Card>
+  )
+}
+
+// SubTables 컴포넌트 - 컨테이너 너비 제한 강화
+function SubTables() {
+  const router = useRouter()
+  const { selectedDocumentId, selectedRevisionId, setSelectedRevisionId, allData, setAllData } =
+    React.useContext(DocumentSelectionContextAll)
+
+  // PDF 뷰어 상태 관리
+  const [viewerOpen, setViewerOpen] = React.useState(false)
+  const [selectedRevision, setSelectedRevision] = React.useState<RevisionInfo | null>(null)
+  const [instance, setInstance] = React.useState<WebViewerInstance | null>(null)
+  const [viewerLoading, setViewerLoading] = React.useState(true)
+  const [fileSetLoading, setFileSetLoading] = React.useState(true)
+  const viewer = React.useRef<HTMLDivElement>(null)
+  const initialized = React.useRef(false)
+  const isCancelled = React.useRef(false)
+
+  const selectedDocument = React.useMemo(() => {
+    if (!selectedDocumentId || !allData) return null
+    return allData.find((d) => d.documentId === selectedDocumentId) || null
+  }, [selectedDocumentId, allData])
+
+  const allRevisions = React.useMemo(() => {
+    if (!selectedDocument?.allStages) return []
+
+    const revisions: RevisionInfo[] = []
+    for (const stage of selectedDocument.allStages as StageInfo[]) {
+      const stageRevisions = stage.revisions.map(revision => ({
+        ...revision,
+        stageName: stage.stageName
+      }))
+      revisions.push(...stageRevisions)
+    }
+
+    return revisions.sort((a, b) =>
+      new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
+    )
+  }, [selectedDocument])
+
+  const selectedRevisionData = React.useMemo(() => {
+    if (!selectedRevisionId) return null
+    return allRevisions.find(r => r.id === selectedRevisionId) || null
+  }, [selectedRevisionId, allRevisions])
+
+  const cleanupHtmlStyle = React.useCallback(() => {
+    const htmlElement = window.document.documentElement
+    const originalStyle = htmlElement.getAttribute("style") || ""
+    const colorSchemeStyle = originalStyle
+      .split(";")
+      .map((s) => s.trim())
+      .find((s) => s.startsWith("color-scheme:"))
+
+    if (colorSchemeStyle) {
+      htmlElement.setAttribute("style", colorSchemeStyle + ";")
+    } else {
+      htmlElement.removeAttribute("style")
+    }
+  }, [])
+
+  const handleViewRevision = React.useCallback((revision: RevisionInfo) => {
+    setSelectedRevision(revision)
+    setViewerOpen(true)
+    setViewerLoading(true)
+    setFileSetLoading(true)
+    initialized.current = false
+  }, [])
+
+  const handleDownloadFile = React.useCallback(async (attachment: AttachmentInfo) => {
+    try {
+      const queryParam = attachment.id
+        ? `id=${encodeURIComponent(attachment.id)}`
+        : `path=${encodeURIComponent(attachment.filePath)}`
+
+      const response = await fetch(`/api/document-download?${queryParam}`)
+
+      if (!response.ok) {
+        const errorData = await response.json()
+        throw new Error(errorData.error || 'Failed to download file.')
+      }
+
+      const blob = await response.blob()
+      const url = window.URL.createObjectURL(blob)
+      const link = window.document.createElement('a')
+      link.href = url
+      link.download = attachment.fileName
+      window.document.body.appendChild(link)
+      link.click()
+      window.document.body.removeChild(link)
+      window.URL.revokeObjectURL(url)
+    } catch (error) {
+      console.error('File download error:', error)
+      alert(`File download failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
+    }
+  }, [])
+
+  // WebViewer 초기화
+  React.useEffect(() => {
+    if (viewerOpen && !initialized.current) {
+      initialized.current = true
+      isCancelled.current = false
+
+      requestAnimationFrame(() => {
+        if (viewer.current && !isCancelled.current) {
+          import("@pdftron/webviewer").then(({ default: WebViewer }) => {
+            if (isCancelled.current) {
+              console.log("WebViewer initialization cancelled (Dialog closed)")
+              return
+            }
+
+            WebViewer(
+              {
+                path: "/pdftronWeb",
+                licenseKey: "demo:1739264618684:616161d7030000000091db1c97c6f386d41d3506ab5b507381ef2ee2bd",
+                fullAPI: true,
+                css: "/globals.css",
+              },
+              viewer.current as HTMLDivElement
+            ).then(async (instance: WebViewerInstance) => {
+              if (!isCancelled.current) {
+                setInstance(instance)
+                instance.UI.enableFeatures([instance.UI.Feature.MultiTab])
+                instance.UI.disableElements(["addTabButton", "multiTabsEmptyPage"])
+                setViewerLoading(false)
+              }
+            })
+          })
+        }
+      })
+    }
+
+    return () => {
+      if (instance) {
+        instance.UI.dispose()
+      }
+      setTimeout(() => cleanupHtmlStyle(), 500)
+    }
+  }, [viewerOpen, cleanupHtmlStyle, instance])
+
+  // 문서 로드
+  React.useEffect(() => {
+    const loadDocument = async () => {
+      if (instance && selectedRevision?.attachments?.length) {
+        const { UI } = instance
+
+        const tabIds = []
+        for (const attachment of selectedRevision.attachments) {
+          try {
+            const response = await fetch(attachment.filePath)
+            const blob = await response.blob()
+            const options = {
+              filename: attachment.fileName,
+              ...(attachment.fileType?.includes("xlsx") && {
+                officeOptions: {
+                  formatOptions: {
+                    applyPageBreaksToSheet: true,
+                  },
+                },
+              }),
+            }
+            const tab = await UI.TabManager.addTab(blob, options)
+            tabIds.push(tab)
+          } catch (error) {
+            console.error("File load failed:", attachment.filePath, error)
+          }
+        }
+
+        if (tabIds.length > 0) {
+          await UI.TabManager.setActiveTab(tabIds[0])
+        }
+
+        setFileSetLoading(false)
+      }
+    }
+    loadDocument()
+  }, [instance, selectedRevision])
+
+  const handleCloseViewer = React.useCallback(async () => {
+    if (!fileSetLoading) {
+      isCancelled.current = true
+
+      if (instance) {
+        try {
+          await instance.UI.dispose()
+          setInstance(null)
+        } catch (e) {
+          console.warn("dispose error", e)
+        }
+      }
+
+      setViewerLoading(false)
+      setViewerOpen(false)
+      setTimeout(() => cleanupHtmlStyle(), 1000)
+    }
+  }, [fileSetLoading, instance, cleanupHtmlStyle])
+
+  if (!selectedDocument) return null
+
+  return (
+    <>
+      {/* 컨테이너 너비 제한 강화 */}
+      <div className="w-full max-w-full overflow-hidden">
+        <div className="flex flex-col lg:flex-row gap-4 min-w-0">
+          <RevisionTable
+            revisions={allRevisions}
+            onViewRevision={handleViewRevision}
+          />
+          <AttachmentTable
+            attachments={selectedRevisionData?.attachments || []}
+            onDownloadFile={handleDownloadFile}
+          />
+        </div>
+      </div>
+
+      {/* 문서 뷰어 다이얼로그 */}
+      <Dialog open={viewerOpen} onOpenChange={handleCloseViewer}>
+        <DialogContent className="w-[90vw] h-[90vh]" style={{ maxWidth: "none" }}>
+          <DialogHeader className="h-[38px]">
+            <DialogTitle>Document Preview</DialogTitle>
+            <DialogDescription>
+              Revision {selectedRevision?.revision} attachments
+            </DialogDescription>
+          </DialogHeader>
+          <div
+            ref={viewer}
+            style={{ height: "calc(90vh - 20px - 38px - 1rem - 48px)" }}
+          >
+            {viewerLoading && (
+              <div className="flex flex-col items-center justify-center py-12">
+                <Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-4" />
+                <p className="text-sm text-muted-foreground">
+                  Loading document viewer...
+                </p>
+              </div>
+            )}
+          </div>
+        </DialogContent>
+      </Dialog>
+    </>
+  )
+}
+
+/* -------------------------------------------------------------------------------------------------
+ * High‑level Selected Document Summary
+ * -----------------------------------------------------------------------------------------------*/
+function SelectedDocumentInfo() {
+  const { selectedDocumentId, selectedRevisionId, allData } =
+    React.useContext(DocumentSelectionContextAll)
+
+  if (!selectedDocumentId || !allData) return null
+
+  const doc = allData.find((d) => d.documentId === selectedDocumentId)
+  if (!doc) return null
+
+  const totalRevisions = doc.allStages
+    ? (doc.allStages as StageInfo[]).reduce(
+      (acc, s) => acc + s.revisions.length,
+      0,
+    )
+    : 0
+
+  let selectedRevision: RevisionInfo | null = null
+  if (selectedRevisionId && doc.allStages) {
+    for (const stage of doc.allStages as StageInfo[]) {
+      const rev = stage.revisions.find((r) => r.id === selectedRevisionId)
+      if (rev) {
+        selectedRevision = rev
+        break
+      }
+    }
+  }
+
+  return (
+    <div className="w-full max-w-full overflow-hidden">
+      <div className="flex flex-wrap items-center gap-3 rounded-lg bg-gray-50 p-4">
+        <div className="flex items-center gap-2 min-w-0">
+          <Badge variant="secondary" className="text-sm flex-shrink-0">
+            Document: {doc.docNumber}
+          </Badge>
+          <span className="max-w-[300px] truncate text-sm font-medium text-gray-700">
+            {doc.title}
+          </span>
+        </div>
+        <div className="flex items-center gap-2 text-sm text-gray-600">
+          <span>•</span>
+          <span>Total {totalRevisions} revisions</span>
+          {selectedRevision && (
+            <>
+              <span>•</span>
+              <Badge variant="outline" className="text-sm">
+                Selected revision: {selectedRevision.revision}
+              </Badge>
+              <span>({selectedRevision.attachments.length} files)</span>
+            </>
+          )}
+        </div>
+      </div>
+    </div>
+  )
+}
+
+/* -------------------------------------------------------------------------------------------------
+ * Main Exported Component
+ * -----------------------------------------------------------------------------------------------*/
+export function UserVendorALLDocumentDisplay({
+  allPromises,
+}: UserVendorDocumentDisplayProps) {
+  const [selectedDocumentId, setSelectedDocumentId] =
+    React.useState<number | null>(null)
+  const [selectedStageId, setSelectedStageId] = React.useState<number | null>(
+    null,
+  )
+  const [selectedRevisionId, setSelectedRevisionId] =
+    React.useState<number | null>(null)
+  const [allData, setAllData] =
+    React.useState<SimplifiedDocumentsView[] | null>(null)
+
+  const handleDocumentSelect = React.useCallback((id: number | null) => {
+    setSelectedDocumentId(id)
+    setSelectedStageId(null)
+    setSelectedRevisionId(null)
+  }, [])
+
+  const ctx = React.useMemo<DocumentSelectionContextType>(
+    () => ({
+      selectedDocumentId,
+      selectedStageId,
+      selectedRevisionId,
+      setSelectedDocumentId: handleDocumentSelect,
+      setSelectedStageId,
+      setSelectedRevisionId,
+      allData,
+      setAllData,
+    }),
+    [
+      selectedDocumentId,
+      selectedStageId,
+      selectedRevisionId,
+      handleDocumentSelect,
+      allData,
+      setAllData,
+    ],
+  )
+
+  if (!allPromises) {
+    return (
+      <Card>
+        <CardContent className="flex items-center justify-center py-8">
+          <div className="text-center">
+            <AlertCircle className="mx-auto mb-2 h-8 w-8 text-gray-400" />
+            <p className="text-gray-600">Unable to load data.</p>
+          </div>
+        </CardContent>
+      </Card>
+    )
+  }
+
+  return (
+    <DocumentSelectionContextAll.Provider value={ctx}>
+      <div className="space-y-4 w-full max-w-full overflow-hidden">
+        <Card className="w-full max-w-full">
+          <CardContent className="p-4 overflow-hidden">
+            <div className="w-full max-w-full">
+              <SimplifiedDocumentsTable
+                allPromises={allPromises}
+                onDataLoaded={setAllData}
+                onDocumentSelect={handleDocumentSelect}
+              />
+            </div>
+          </CardContent>
+        </Card> 
+        <SelectedDocumentInfo />
+        <SubTables />
+      </div>
+    </DocumentSelectionContextAll.Provider>
+  )
+}
\ No newline at end of file
-- 
cgit v1.2.3

