diff options
Diffstat (limited to 'lib/vendor-document-list')
| -rw-r--r-- | lib/vendor-document-list/table/enhanced-documents-table.tsx | 48 | ||||
| -rw-r--r-- | lib/vendor-document-list/table/stage-revision-expanded-content.tsx | 171 |
2 files changed, 117 insertions, 102 deletions
diff --git a/lib/vendor-document-list/table/enhanced-documents-table.tsx b/lib/vendor-document-list/table/enhanced-documents-table.tsx index 14c52455..f840a10c 100644 --- a/lib/vendor-document-list/table/enhanced-documents-table.tsx +++ b/lib/vendor-document-list/table/enhanced-documents-table.tsx @@ -468,26 +468,34 @@ export function EnhancedDocumentsTable({ {/* 메인 테이블 - 가로스크롤 문제 해결을 위한 구조 개선 */} <div className="space-y-4"> <div className="rounded-md border bg-white overflow-hidden"> - <ExpandableDataTable - table={table} - expandable={true} - expandedRows={expandedRows} - setExpandedRows={setExpandedRows} - renderExpandedContent={(document) => ( - // ✅ 확장된 내용을 별도 컨테이너로 분리하여 가로스크롤 영향 차단 - <div className=""> - <StageRevisionExpandedContent - document={document} - onUploadRevision={handleUploadRevision} - projectType={projectType} - expandedStages={expandedStages[String(document.documentId)] || {}} - onStageToggle={(stageId) => handleStageToggle(String(document.documentId), stageId)} - /> - </div> - )} - // 확장된 행에 대한 특별한 스타일링 - expandedRowClassName="!p-0" - > + <ExpandableDataTable + table={table} + expandable={true} + expandedRows={expandedRows} + setExpandedRows={setExpandedRows} + renderExpandedContent={(document) => ( + <div className=""> + <StageRevisionExpandedContent + document={document} + onUploadRevision={handleUploadRevision} + projectType={projectType} + expandedStages={expandedStages[String(document.documentId)] || {}} + onStageToggle={(stageId) => handleStageToggle(String(document.documentId), stageId)} + /> + </div> + )} + expandedRowClassName="!p-0" + // clickableColumns={[ + // 'docNumber', + // 'title', + // 'currentStageStatus', + // 'progressPercentage', + // ]} + excludeFromClick={[ + 'actions', + 'select' + ]} + > <DataTableAdvancedToolbar table={table} filterFields={advancedFilterFields} diff --git a/lib/vendor-document-list/table/stage-revision-expanded-content.tsx b/lib/vendor-document-list/table/stage-revision-expanded-content.tsx index d9d53cc9..a4de03b7 100644 --- a/lib/vendor-document-list/table/stage-revision-expanded-content.tsx +++ b/lib/vendor-document-list/table/stage-revision-expanded-content.tsx @@ -13,7 +13,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog" -import { +import { Table, TableBody, TableCell, @@ -21,7 +21,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table" -import { +import { FileText, User, Calendar, @@ -86,7 +86,7 @@ const getPriorityText = (priority: string) => { const getFileIconColor = (fileName: string) => { const ext = fileName.split('.').pop()?.toLowerCase() - switch(ext) { + switch (ext) { case 'pdf': return 'text-red-500' case 'doc': case 'docx': return 'text-blue-500' case 'xls': case 'xlsx': return 'text-green-500' @@ -105,7 +105,7 @@ interface StageRevisionExpandedContentProps { onStageToggle?: (stageId: number) => void } -export const StageRevisionExpandedContent = ({ +export const StageRevisionExpandedContent = ({ document: documentData, onUploadRevision, onStageStatusUpdate, @@ -117,7 +117,7 @@ export const StageRevisionExpandedContent = ({ // 로컬 상태 관리 const [localExpandedStages, setLocalExpandedStages] = React.useState<Record<number, boolean>>({}) const [expandedRevisions, setExpandedRevisions] = React.useState<Set<number>>(new Set()) - + // ✅ 문서 뷰어 상태 관리 const [viewerOpen, setViewerOpen] = React.useState(false) const [selectedRevisions, setSelectedRevisions] = React.useState<any[]>([]) @@ -127,11 +127,11 @@ export const StageRevisionExpandedContent = ({ const viewer = React.useRef<HTMLDivElement>(null) const initialized = React.useRef(false) const isCancelled = React.useRef(false) - + // 상위에서 관리하는지 로컬에서 관리하는지 결정 const isExternallyManaged = onStageToggle !== undefined const currentExpandedStages = isExternallyManaged ? expandedStages : localExpandedStages - + const handleStageToggle = React.useCallback((stageId: number) => { if (isExternallyManaged && onStageToggle) { onStageToggle(stageId) @@ -157,7 +157,7 @@ export const StageRevisionExpandedContent = ({ // ✅ PDF 뷰어 정리 함수 const cleanupHtmlStyle = React.useCallback(() => { - const htmlElement = window.document.documentElement + const htmlElement = window.document.documentElement const originalStyle = htmlElement.getAttribute("style") || "" const colorSchemeStyle = originalStyle .split(";") @@ -185,12 +185,12 @@ export const StageRevisionExpandedContent = ({ console.log(attachment) try { // ID를 우선으로 사용, 없으면 filePath 사용 - const queryParam = attachment.id + 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 || '파일 다운로드에 실패했습니다.') @@ -205,7 +205,7 @@ export const StageRevisionExpandedContent = ({ link.click() window.document.body.removeChild(link) window.URL.revokeObjectURL(url) - + console.log('✅ 파일 다운로드 완료:', attachment.fileName) } catch (error) { console.error('❌ 파일 다운로드 오류:', error) @@ -312,7 +312,7 @@ export const StageRevisionExpandedContent = ({ const handleCloseViewer = React.useCallback(async () => { if (!fileSetLoading) { isCancelled.current = true - + if (instance) { try { await instance.UI.dispose() @@ -342,7 +342,7 @@ export const StageRevisionExpandedContent = ({ </div> ) } - + return ( <> <div className="w-full max-w-none bg-gray-50" onClick={(e) => e.stopPropagation()}> @@ -366,27 +366,28 @@ export const StageRevisionExpandedContent = ({ 새 리비전 업로드 </Button> */} </div> - + <ScrollArea className="h-[400px] w-full"> <div className="space-y-3 pr-4"> {stagesWithRevisions.map((stage) => { const isExpanded = currentExpandedStages[stage.id] || false const revisions = stage.revisions || [] - + return ( <div key={stage.id} className="bg-white rounded border shadow-sm overflow-hidden"> - {/* 스테이지 헤더 */} - <div className="py-2 px-3 bg-gray-50 border-b"> + {/* 스테이지 헤더 - 전체 영역 클릭 가능 */} + <div + className="py-2 px-3 bg-gray-50 border-b cursor-pointer hover:bg-gray-100 transition-colors" + onClick={(e) => { + e.preventDefault() + e.stopPropagation() + handleStageToggle(stage.id) + }} + > <div className="flex items-center justify-between"> <div className="flex items-center gap-3"> - <button - className="flex items-center gap-2 hover:bg-gray-100 p-1 rounded transition-colors" - onClick={(e) => { - e.preventDefault() - e.stopPropagation() - handleStageToggle(stage.id) - }} - > + {/* 버튼 영역 - 이제 시각적 표시만 담당 */} + <div className="flex items-center gap-2"> <div className="flex items-center gap-2"> <div className="w-6 h-6 rounded-full bg-white border-2 border-gray-300 flex items-center justify-center text-xs font-medium"> {stage.stageOrder || 1} @@ -394,17 +395,17 @@ export const StageRevisionExpandedContent = ({ <div className={cn( "w-2 h-2 rounded-full", stage.stageStatus === 'COMPLETED' ? 'bg-green-500' : - stage.stageStatus === 'IN_PROGRESS' ? 'bg-blue-500' : - stage.stageStatus === 'SUBMITTED' ? 'bg-purple-500' : - 'bg-gray-300' + stage.stageStatus === 'IN_PROGRESS' ? 'bg-blue-500' : + stage.stageStatus === 'SUBMITTED' ? 'bg-purple-500' : + 'bg-gray-300' )} /> - {isExpanded ? - <ChevronDown className="w-3 h-3 text-gray-500" /> : + {isExpanded ? + <ChevronDown className="w-3 h-3 text-gray-500" /> : <ChevronRight className="w-3 h-3 text-gray-500" /> } </div> - </button> - + </div> + <div className="flex-1"> <div className="flex items-center gap-2"> <div className="font-medium text-sm">{stage.stageName}</div> @@ -417,7 +418,7 @@ export const StageRevisionExpandedContent = ({ </div> </div> </div> - + <div className="flex items-center gap-4"> <div className="grid grid-cols-2 gap-2 text-xs"> <div> @@ -437,45 +438,51 @@ export const StageRevisionExpandedContent = ({ </div> )} </div> - - {/* 스테이지 액션 메뉴 */} - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button - variant="ghost" - size="sm" - className="h-7 w-7 p-0" - > - <MoreHorizontal className="h-3 w-3" /> - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent align="end"> - {onStageStatusUpdate && ( - <> - <DropdownMenuItem onClick={() => onStageStatusUpdate(stage.id, 'IN_PROGRESS')}> - 진행 시작 - </DropdownMenuItem> - <DropdownMenuItem onClick={() => onStageStatusUpdate(stage.id, 'COMPLETED')}> - 완료 처리 - </DropdownMenuItem> - </> - )} - <DropdownMenuItem onClick={() => onUploadRevision(documentData, stage.stageName)}> - 리비전 업로드 - </DropdownMenuItem> - {/* ✅ 스테이지에 첨부파일이 있는 리비전이 있을 때만 문서 보기 버튼 표시 */} - {revisions.some(rev => rev.attachments && rev.attachments.length > 0) && ( - <DropdownMenuItem onClick={() => handleViewRevision(revisions.filter(rev => rev.attachments && rev.attachments.length > 0))}> - <Eye className="w-3 h-3 mr-1" /> - 스테이지 문서 보기 + + {/* 스테이지 액션 메뉴 - 클릭 이벤트 전파 차단 */} + <div + onClick={(e) => { + e.stopPropagation() // 액션 메뉴 클릭 시 스테이지 토글 방지 + }} + > + <DropdownMenu> + <DropdownMenuTrigger asChild> + <Button + variant="ghost" + size="sm" + className="h-7 w-7 p-0" + > + <MoreHorizontal className="h-3 w-3" /> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end"> + {onStageStatusUpdate && ( + <> + <DropdownMenuItem onClick={() => onStageStatusUpdate(stage.id, 'IN_PROGRESS')}> + 진행 시작 + </DropdownMenuItem> + <DropdownMenuItem onClick={() => onStageStatusUpdate(stage.id, 'COMPLETED')}> + 완료 처리 + </DropdownMenuItem> + </> + )} + <DropdownMenuItem onClick={() => onUploadRevision(documentData, stage.stageName)}> + 리비전 업로드 </DropdownMenuItem> - )} - </DropdownMenuContent> - </DropdownMenu> + {/* ✅ 스테이지에 첨부파일이 있는 리비전이 있을 때만 문서 보기 버튼 표시 */} + {revisions.some(rev => rev.attachments && rev.attachments.length > 0) && ( + <DropdownMenuItem onClick={() => handleViewRevision(revisions.filter(rev => rev.attachments && rev.attachments.length > 0))}> + <Eye className="w-3 h-3 mr-1" /> + 스테이지 문서 보기 + </DropdownMenuItem> + )} + </DropdownMenuContent> + </DropdownMenu> + </div> </div> </div> </div> - + {/* 리비전 목록 - 테이블 형태 */} {isExpanded && ( <div className="max-h-72 overflow-y-auto"> @@ -499,29 +506,29 @@ export const StageRevisionExpandedContent = ({ <TableBody> {revisions.map((revision) => { const hasAttachments = revision.attachments && revision.attachments.length > 0 - + return ( <TableRow key={revision.id} className="hover:bg-gray-50 h-10"> {/* 리비전 */} <TableCell className="py-1 px-2"> <span className="text-xs font-semibold"> - {revision.uploaderType ==="vendor"?"To SHI":"From SHI"} + {revision.uploaderType === "vendor" ? "To SHI" : "From SHI"} </span> </TableCell> - + <TableCell className="py-1 px-2"> <span className="font-mono text-xs font-semibold bg-gray-100 px-1.5 py-0.5 rounded"> {revision.revision} </span> </TableCell> - + {/* 상태 */} <TableCell className="py-1 px-2"> <Badge className={cn("text-xs px-1.5 py-0.5", getStatusColor(revision.revisionStatus))}> {getStatusText(revision.revisionStatus)} </Badge> </TableCell> - + {/* 업로더 */} <TableCell className="py-1 px-2"> <div className="flex items-center gap-1"> @@ -529,20 +536,20 @@ export const StageRevisionExpandedContent = ({ <span className="text-xs truncate max-w-[60px]">{revision.uploaderName || '-'}</span> </div> </TableCell> - {/* 제출일 */} + {/* 제출일 */} <TableCell className="py-1 px-2"> <span className="text-xs text-gray-600"> {revision.uploadedAt ? formatDate(revision.uploadedAt) : '-'} </span> </TableCell> - + {/* 제출일 */} <TableCell className="py-1 px-2"> <span className="text-xs text-gray-600"> {revision.externalSentDate ? formatDate(revision.externalSentDate) : '-'} </span> </TableCell> - + {/* 승인/반려일 */} <TableCell className="py-1 px-2"> <div className="text-xs text-gray-600"> @@ -569,7 +576,7 @@ export const StageRevisionExpandedContent = ({ )} </div> </TableCell> - + {/* ✅ 첨부파일 - 클릭 시 다운로드, 별도 뷰어 버튼 */} <TableCell className="py-1 px-2"> {hasAttachments ? ( @@ -588,7 +595,7 @@ export const StageRevisionExpandedContent = ({ </Button> ))} {revision.attachments.length > 4 && ( - <span + <span className="text-xs text-gray-500 ml-0.5" title={`총 ${revision.attachments.length}개 파일`} > @@ -610,7 +617,7 @@ export const StageRevisionExpandedContent = ({ <span className="text-gray-400 text-xs">-</span> )} </TableCell> - + {/* 액션 */} <TableCell className="py-1 px-2"> <div className="flex gap-0.5"> @@ -647,7 +654,7 @@ export const StageRevisionExpandedContent = ({ </Button> </div> </TableCell> - + {/* 코멘트 */} <TableCell className="py-1 px-2"> {revision.comment ? ( @@ -703,7 +710,7 @@ export const StageRevisionExpandedContent = ({ <DialogHeader className="h-[38px]"> <DialogTitle>문서 미리보기</DialogTitle> <DialogDescription> - {selectedRevisions.length === 1 + {selectedRevisions.length === 1 ? `리비전 ${selectedRevisions[0]?.revision} 첨부파일` : `${selectedRevisions.length}개 리비전 첨부파일` } |
