"use client" import * as React from "react" import type { DataTableAdvancedFilterField, DataTableFilterField, DataTableRowAction, } from "@/types/table" import { useDataTable } from "@/hooks/use-data-table" import { StageRevisionExpandedContent } from "./stage-revision-expanded-content" import { RevisionUploadDialog } from "./revision-upload-dialog" // ✅ UpdateDocumentSheet import 추가 import { EnhancedDocTableToolbarActions } from "./enhanced-doc-table-toolbar-actions" import { getEnhancedDocuments } from "../enhanced-document-service" import type { EnhancedDocument } from "@/types/enhanced-documents" import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar" import { Badge } from "@/components/ui/badge" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { AlertTriangle, Clock, TrendingUp, Target, Users, } from "lucide-react" import { getUpdatedEnhancedColumns } from "./enhanced-doc-table-columns" import { ExpandableDataTable } from "@/components/data-table/expandable-data-table" import { toast } from "sonner" import { UpdateDocumentSheet } from "./update-doc-sheet" interface FinalIntegratedDocumentsTableProps { promises: Promise<[Awaited>]> selectedPackageId: number projectType: "ship" | "plant" // ✅ contractId 추가 (AddDocumentListDialog에서 필요) contractId: number } export function EnhancedDocumentsTable({ promises, selectedPackageId, projectType, contractId, // ✅ contractId 추가 }: FinalIntegratedDocumentsTableProps) { // 데이터 로딩 const [{ data, pageCount, total }] = React.use(promises) // 상태 관리 const [rowAction, setRowAction] = React.useState | null>(null) const [expandedRows, setExpandedRows] = React.useState>(new Set()) const [quickFilter, setQuickFilter] = React.useState<'all' | 'overdue' | 'due_soon' | 'in_progress' | 'high_priority'>('all') // ✅ 스테이지 확장 상태 관리 (문서별로 관리) const [expandedStages, setExpandedStages] = React.useState>>({}) // ✅ 다이얼로그 상태들 - editDialogOpen -> editSheetOpen으로 변경 const [uploadDialogOpen, setUploadDialogOpen] = React.useState(false) const [editSheetOpen, setEditSheetOpen] = React.useState(false) // Sheet로 변경 const [selectedDocument, setSelectedDocument] = React.useState(null) const [selectedStage, setSelectedStage] = React.useState("") const [selectedRevision, setSelectedRevision] = React.useState("") const [uploadMode, setUploadMode] = React.useState<'new' | 'append'>('new') // 다음 리비전 계산 함수 const getNextRevision = React.useCallback((currentRevision: string): string => { if (!currentRevision) return "A" // 알파벳 리비전 (A, B, C...) if (/^[A-Z]$/.test(currentRevision)) { const charCode = currentRevision.charCodeAt(0) if (charCode < 90) { // Z가 아닌 경우 return String.fromCharCode(charCode + 1) } return "AA" // Z 다음은 AA } // 숫자 리비전 (1, 2, 3...) if (/^\d+$/.test(currentRevision)) { return String(parseInt(currentRevision) + 1) } // 기타 복잡한 리비전 형태는 그대로 반환 return currentRevision }, []) // 컬럼 정의 const columns = React.useMemo( () => getUpdatedEnhancedColumns({ setRowAction: (action) => { setRowAction(action) if (action) { setSelectedDocument(action.row.original) // 액션 타입에 따른 다이얼로그 열기 switch (action.type) { case "update": setEditSheetOpen(true) // ✅ Sheet 열기로 변경 break case "upload": setSelectedStage(action.row.original.currentStageName || "") setUploadDialogOpen(true) break case "view": // 상세보기는 확장된 행으로 대체 const rowId = action.row.id const newExpanded = new Set(expandedRows) if (newExpanded.has(rowId)) { newExpanded.delete(rowId) } else { newExpanded.add(rowId) } setExpandedRows(newExpanded) break } } }, projectType }), [expandedRows, projectType] ) // 통계 계산 const stats = React.useMemo(() => { const totalDocs = data.length const overdue = data.filter(doc => doc.isOverdue).length const dueSoon = data.filter(doc => doc.daysUntilDue !== null && doc.daysUntilDue >= 0 && doc.daysUntilDue <= 3 ).length const inProgress = data.filter(doc => doc.currentStageStatus === 'IN_PROGRESS').length const highPriority = data.filter(doc => doc.currentStagePriority === 'HIGH').length const avgProgress = totalDocs > 0 ? Math.round(data.reduce((sum, doc) => sum + (doc.progressPercentage || 0), 0) / totalDocs) : 0 return { total: totalDocs, overdue, dueSoon, inProgress, highPriority, avgProgress } }, [data]) // 빠른 필터링 const filteredData = React.useMemo(() => { switch (quickFilter) { case 'overdue': return data.filter(doc => doc.isOverdue) case 'due_soon': return data.filter(doc => doc.daysUntilDue !== null && doc.daysUntilDue >= 0 && doc.daysUntilDue <= 3 ) case 'in_progress': return data.filter(doc => doc.currentStageStatus === 'IN_PROGRESS') case 'high_priority': return data.filter(doc => doc.currentStagePriority === 'HIGH') default: return data } }, [data, quickFilter]) // ✅ 핸들러 함수 수정: 모드 매개변수 추가 const handleUploadRevision = React.useCallback((document: EnhancedDocument, stageName?: string, currentRevision?: string, mode: 'new' | 'append' = 'new') => { setSelectedDocument(document) setSelectedStage(stageName || document.currentStageName || "") setUploadMode(mode) // ✅ 모드 설정 if (mode === 'new') { // 새 리비전 생성: currentRevision이 있으면 다음 리비전을 자동 계산 if (currentRevision) { const nextRevision = getNextRevision(currentRevision) setSelectedRevision(nextRevision) } else { // 스테이지의 최신 리비전을 찾아서 다음 리비전 계산 const latestRevision = findLatestRevisionInStage(document, stageName || document.currentStageName || "") if (latestRevision) { setSelectedRevision(getNextRevision(latestRevision)) } else { setSelectedRevision("A") // 첫 번째 리비전 } } } else { // 기존 리비전에 파일 추가: 같은 리비전 번호 사용 setSelectedRevision(currentRevision || "") } setUploadDialogOpen(true) }, [getNextRevision]) // ✅ 스테이지에서 최신 리비전을 찾는 헬퍼 함수 const findLatestRevisionInStage = React.useCallback((document: EnhancedDocument, stageName: string) => { const stage = document.allStages?.find(s => s.stageName === stageName) if (!stage || !stage.revisions || stage.revisions.length === 0) { return null } // 리비전들을 정렬해서 최신 것 찾기 (간단한 알파벳/숫자 정렬) const sortedRevisions = [...stage.revisions].sort((a, b) => { // 알파벳과 숫자를 구분해서 정렬 const aIsAlpha = /^[A-Z]+$/.test(a.revision) const bIsAlpha = /^[A-Z]+$/.test(b.revision) if (aIsAlpha && bIsAlpha) { return a.revision.localeCompare(b.revision) } else if (!aIsAlpha && !bIsAlpha) { return parseInt(a.revision) - parseInt(b.revision) } else { return aIsAlpha ? -1 : 1 // 알파벳이 숫자보다 먼저 } }) return sortedRevisions[sortedRevisions.length - 1]?.revision || null }, []) // ✅ 새 문서 추가 핸들러 - EnhancedDocTableToolbarActions에서 AddDocumentListDialog를 직접 렌더링하므로 별도 상태 관리 불필요 const handleNewDocument = () => { // AddDocumentListDialog는 자체적으로 Dialog trigger를 가지므로 별도 처리 불필요 // EnhancedDocTableToolbarActions에서 처리됨 } // ✅ 스테이지 토글 핸들러 추가 const handleStageToggle = React.useCallback((documentId: string, stageId: number) => { setExpandedStages(prev => ({ ...prev, [documentId]: { ...prev[documentId], [stageId]: !prev[documentId]?.[stageId] } })) }, []) const handleBulkAction = async (action: string, selectedRows: any[]) => { try { if (action === 'bulk_approve') { // 일괄 승인 로직 const stageIds = selectedRows .map(row => row.original.currentStageId) .filter(Boolean) if (stageIds.length > 0) { // await bulkUpdateStageStatus(stageIds, 'APPROVED') toast.success(`${stageIds.length}개 항목이 승인되었습니다.`) } } else if (action === 'bulk_upload') { // 일괄 업로드 로직 toast.info("일괄 업로드 기능은 준비 중입니다.") } } catch (error) { toast.error("일괄 작업 중 오류가 발생했습니다.") } } // ✅ 다이얼로그 닫기 함수 수정 const closeAllDialogs = () => { setUploadDialogOpen(false) setEditSheetOpen(false) // editDialogOpen -> editSheetOpen setSelectedDocument(null) setSelectedStage("") setSelectedRevision("") setUploadMode('new') // ✅ 모드 초기화 setRowAction(null) } // ✅ EnhancedDocument를 UpdateDocumentSheet의 document 형식으로 변환하는 함수 const convertToUpdateFormat = React.useCallback((doc: EnhancedDocument | null) => { if (!doc) return null return { id: doc.documentId, contractId: contractId, // contractId 사용 docNumber: doc.docNumber, title: doc.title, status: doc.status || "pending", // 기본값 설정 description: doc.description || null, remarks: doc.remarks || null, } }, [contractId]) // 필터 필드 정의 const filterFields: DataTableFilterField[] = [ { label: "문서번호", value: "docNumber", placeholder: "문서번호로 검색...", }, { label: "제목", value: "title", placeholder: "제목으로 검색...", }, ] const advancedFilterFields: DataTableAdvancedFilterField[] = [ { id: "docNumber", label: "문서번호", type: "text", }, { id: "title", label: "문서제목", type: "text", }, { id: "currentStageStatus", label: "스테이지 상태", type: "select", options: [ { label: "계획됨", value: "PLANNED" }, { label: "진행중", value: "IN_PROGRESS" }, { label: "제출됨", value: "SUBMITTED" }, { label: "승인됨", value: "APPROVED" }, { label: "완료됨", value: "COMPLETED" }, ], }, { id: "currentStagePriority", label: "우선순위", type: "select", options: [ { label: "높음", value: "HIGH" }, { label: "보통", value: "MEDIUM" }, { label: "낮음", value: "LOW" }, ], }, { id: "isOverdue", label: "지연 여부", type: "select", options: [ { label: "지연됨", value: "true" }, { label: "정상", value: "false" }, ], }, { id: "currentStageAssigneeName", label: "담당자", type: "text", }, { id: "createdAt", label: "생성일", type: "date", }, ] // 데이터 테이블 훅 const { table } = useDataTable({ data: filteredData, columns, pageCount, filterFields, enablePinning: true, enableAdvancedFilter: true, initialState: { sorting: [{ id: "createdAt", desc: true }], columnPinning: { right: ["actions"] }, }, getRowId: (originalRow) => String(originalRow.documentId), shallow: false, clearOnDefault: true, columnResizeMode: "onEnd", }) return (
{/* 통계 대시보드 */}
setQuickFilter('all')}> 전체 문서
{stats.total}

총 {total}개 중 {stats.total}개 표시

setQuickFilter('overdue')}> 지연 문서
{stats.overdue}

즉시 확인 필요

setQuickFilter('due_soon')}> 마감 임박
{stats.dueSoon}

3일 이내 마감

평균 진행률
{stats.avgProgress}%

전체 프로젝트 진행도

{/* 빠른 필터 */}
setQuickFilter('all')} > 전체 ({stats.total}) setQuickFilter('overdue')} > 지연 ({stats.overdue}) setQuickFilter('due_soon')} > 마감임박 ({stats.dueSoon}) setQuickFilter('in_progress')} > 진행중 ({stats.inProgress}) setQuickFilter('high_priority')} > 높은우선순위 ({stats.highPriority})
{/* 메인 테이블 - 가로스크롤 문제 해결을 위한 구조 개선 */}
(
handleStageToggle(String(document.documentId), stageId)} />
)} expandedRowClassName="!p-0" // clickableColumns={[ // 'docNumber', // 'title', // 'currentStageStatus', // 'progressPercentage', // ]} excludeFromClick={[ 'actions', 'select' ]} >
{/* ✅ 분리된 다이얼로그들 - UpdateDocumentSheet와 AddDocumentListDialog로 교체 */} {/* 리비전 업로드 다이얼로그 - mode props 추가 */} { if (!open) closeAllDialogs() else setUploadDialogOpen(open) }} document={selectedDocument} projectType={projectType} presetStage={selectedStage} presetRevision={selectedRevision} mode={uploadMode} /> {/* ✅ 문서 편집 Sheet로 교체 */} { if (!open) closeAllDialogs() else setEditSheetOpen(open) }} document={convertToUpdateFormat(selectedDocument)} />
) }