// enhanced-documents-table-with-drawing-filter.tsx "use client" import 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" 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, Settings, Filter } 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" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Label } from "@/components/ui/label" interface FinalIntegratedDocumentsTableProps { promises: Promise<[Awaited>]> selectedPackageId: number projectType: "ship" | "plant" contractId: number initialDrawingKind?: string } // ✅ Drawing Kind 옵션 정의 const DRAWING_KIND_OPTIONS = [ { value: "all", label: "전체 문서" }, { value: "B3", label: "B3: Vendor" }, { value: "B4", label: "B4: GTT" }, { value: "B5", label: "B5: FMEA" }, ] as const export function EnhancedDocumentsTable({ promises, selectedPackageId, projectType, contractId, initialDrawingKind = "all" }: FinalIntegratedDocumentsTableProps) { const [{ data, pageCount, total }] = React.use(promises) // ✅ Drawing Kind 필터 상태 추가 const [drawingKindFilter, setDrawingKindFilter] = React.useState(initialDrawingKind) // 기존 상태들 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>>({}) const [uploadDialogOpen, setUploadDialogOpen] = React.useState(false) const [editSheetOpen, setEditSheetOpen] = React.useState(false) const [selectedDocument, setSelectedDocument] = React.useState(null) const [selectedStage, setSelectedStage] = React.useState("") const [selectedRevision, setSelectedRevision] = React.useState("") const [uploadMode, setUploadMode] = React.useState<'new' | 'append'>('new') // ✅ Drawing Kind별 데이터 필터링 const filteredByDrawingKind = React.useMemo(() => { if (drawingKindFilter === "all") return data return data.filter(doc => doc.drawingKind === drawingKindFilter) }, [data, drawingKindFilter]) // ✅ Drawing Kind별 통계 계산 const drawingKindStats = React.useMemo(() => { const stats = DRAWING_KIND_OPTIONS.reduce((acc, option) => { if (option.value === "all") { acc[option.value] = data.length } else { acc[option.value] = data.filter(doc => doc.drawingKind === option.value).length } return acc }, {} as Record) return stats }, [data]) // 다음 리비전 계산 함수 const getNextRevision = React.useCallback((currentRevision: string): string => { if (!currentRevision) return "A" if (/^[A-Z]$/.test(currentRevision)) { const charCode = currentRevision.charCodeAt(0) if (charCode < 90) { return String.fromCharCode(charCode + 1) } return "AA" } if (/^\d+$/.test(currentRevision)) { return String(parseInt(currentRevision) + 1) } return currentRevision }, []) // ✅ 컬럼 정의 - drawingKindFilter 추가 const columns = React.useMemo( () => getUpdatedEnhancedColumns({ setRowAction: (action) => { setRowAction(action) if (action) { setSelectedDocument(action.row.original) switch (action.type) { case "update": setEditSheetOpen(true) 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, drawingKindFilter // ✅ 추가 }), [expandedRows, projectType, drawingKindFilter] ) // 기존 통계 계산 const stats = React.useMemo(() => { const totalDocs = filteredByDrawingKind.length const overdue = filteredByDrawingKind.filter(doc => doc.isOverdue).length const dueSoon = filteredByDrawingKind.filter(doc => doc.daysUntilDue !== null && doc.daysUntilDue >= 0 && doc.daysUntilDue <= 3 ).length const inProgress = filteredByDrawingKind.filter(doc => doc.currentStageStatus === 'IN_PROGRESS').length const highPriority = filteredByDrawingKind.filter(doc => doc.currentStagePriority === 'HIGH').length const avgProgress = totalDocs > 0 ? Math.round(filteredByDrawingKind.reduce((sum, doc) => sum + (doc.progressPercentage || 0), 0) / totalDocs) : 0 return { total: totalDocs, overdue, dueSoon, inProgress, highPriority, avgProgress } }, [filteredByDrawingKind]) // 빠른 필터링 const filteredData = React.useMemo(() => { switch (quickFilter) { case 'overdue': return filteredByDrawingKind.filter(doc => doc.isOverdue) case 'due_soon': return filteredByDrawingKind.filter(doc => doc.daysUntilDue !== null && doc.daysUntilDue >= 0 && doc.daysUntilDue <= 3 ) case 'in_progress': return filteredByDrawingKind.filter(doc => doc.currentStageStatus === 'IN_PROGRESS') case 'high_priority': return filteredByDrawingKind.filter(doc => doc.currentStagePriority === 'HIGH') default: return filteredByDrawingKind } }, [filteredByDrawingKind, 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') { 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 }, []) const handleNewDocument = () => { // AddDocumentListDialog는 자체적으로 Dialog trigger를 가지므로 별도 처리 불필요 } 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) { toast.success(`${stageIds.length}개 항목이 승인되었습니다.`) } } else if (action === 'bulk_upload') { toast.info("일괄 업로드 기능은 준비 중입니다.") } } catch (error) { toast.error("일괄 작업 중 오류가 발생했습니다.") } } const closeAllDialogs = () => { setUploadDialogOpen(false) setEditSheetOpen(false) setSelectedDocument(null) setSelectedStage("") setSelectedRevision("") setUploadMode('new') setRowAction(null) } const convertToUpdateFormat = React.useCallback((doc: EnhancedDocument | null) => { if (!doc) return null return { id: doc.documentId, 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: "drawingKind", label: "문서종류", type: "select", options: [ { label: "B3", value: "B3" }, { label: "B4", value: "B4" }, { label: "B5", value: "B5" }, ], }, { 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')}> {drawingKindFilter === "all" ? "전체 문서" : `${DRAWING_KIND_OPTIONS.find(o => o.value === drawingKindFilter)?.label} 문서`}
{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})
{/* 오른쪽: 문서 종류 필터 */}
{/* B4 필드 표시 안내 (아이콘만) */} {drawingKindFilter === "B4" && (
상세정보 확장가능
)}
{/* 메인 테이블 */}
(
handleStageToggle(String(document.documentId), stageId)} // showB4Fields={document.drawingKind === "B4"} />
)} expandedRowClassName="!p-0" excludeFromClick={[ 'actions', 'select' ]} >
{/* 다이얼로그들 */} { if (!open) closeAllDialogs() else setUploadDialogOpen(open) }} document={selectedDocument} projectType={projectType} presetStage={selectedStage} presetRevision={selectedRevision} mode={uploadMode} /> { if (!open) closeAllDialogs() else setEditSheetOpen(open) }} document={convertToUpdateFormat(selectedDocument)} />
) }