From 20800b214145ee6056f94ca18fa1054f145eb977 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 28 May 2025 00:32:31 +0000 Subject: (대표님) lib 파트 커밋 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table/enhanced-doc-table-columns.tsx | 612 +++++++++++++++++++++ 1 file changed, 612 insertions(+) create mode 100644 lib/vendor-document-list/table/enhanced-doc-table-columns.tsx (limited to 'lib/vendor-document-list/table/enhanced-doc-table-columns.tsx') diff --git a/lib/vendor-document-list/table/enhanced-doc-table-columns.tsx b/lib/vendor-document-list/table/enhanced-doc-table-columns.tsx new file mode 100644 index 00000000..534a80a0 --- /dev/null +++ b/lib/vendor-document-list/table/enhanced-doc-table-columns.tsx @@ -0,0 +1,612 @@ +// updated-enhanced-doc-table-columns.tsx +"use client" + +import * as React from "react" +import { ColumnDef } from "@tanstack/react-table" +import { formatDate, formatDateTime } from "@/lib/utils" +import { Checkbox } from "@/components/ui/checkbox" +import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" +import { DataTableRowAction } from "@/types/table" +import { EnhancedDocumentsView } from "@/db/schema/vendorDocu" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { Progress } from "@/components/ui/progress" +import { + Ellipsis, + AlertTriangle, + Clock, + CheckCircle, + Upload, + Calendar, + User, + FileText, + Eye, + Edit, + Trash2 +} from "lucide-react" +import { cn } from "@/lib/utils" + +interface GetColumnsProps { + setRowAction: React.Dispatch | null>> + projectType: string | null +} + +// 유틸리티 함수들 +const getStatusColor = (status: string, isOverdue = false) => { + if (isOverdue) return 'destructive' + switch (status) { + case 'COMPLETED': case 'APPROVED': return 'success' + case 'IN_PROGRESS': return 'default' + case 'SUBMITTED': case 'UNDER_REVIEW': return 'secondary' + case 'REJECTED': return 'destructive' + default: return 'outline' + } +} + +const getPriorityColor = (priority: string) => { + switch (priority) { + case 'HIGH': return 'destructive' + case 'MEDIUM': return 'default' + case 'LOW': return 'secondary' + default: return 'outline' + } +} + +const getStatusText = (status: string) => { + switch (status) { + case 'PLANNED': return '계획됨' + case 'IN_PROGRESS': return '진행중' + case 'SUBMITTED': return '제출됨' + case 'UNDER_REVIEW': return '검토중' + case 'APPROVED': return '승인됨' + case 'REJECTED': return '반려됨' + case 'COMPLETED': return '완료됨' + default: return status + } +} + +const getPriorityText = (priority: string) => { + switch (priority) { + case 'HIGH': return '높음' + case 'MEDIUM': return '보통' + case 'LOW': return '낮음' + default: return priority + } +} + +// 마감일 정보 컴포넌트 +const DueDateInfo = ({ + daysUntilDue, + isOverdue, + className = "" +}: { + daysUntilDue: number | null + isOverdue: boolean + className?: string +}) => { + if (isOverdue && daysUntilDue !== null && daysUntilDue < 0) { + return ( +
+ + {Math.abs(daysUntilDue)}일 지연 +
+ ) + } + + if (daysUntilDue === 0) { + return ( +
+ + 오늘 마감 +
+ ) + } + + if (daysUntilDue && daysUntilDue > 0 && daysUntilDue <= 3) { + return ( +
+ + {daysUntilDue}일 남음 +
+ ) + } + + if (daysUntilDue && daysUntilDue > 0) { + return ( +
+ + {daysUntilDue}일 남음 +
+ ) + } + + return ( +
+ + 완료 +
+ ) +} + +export function getUpdatedEnhancedColumns({ + setRowAction, + projectType +}: GetColumnsProps): ColumnDef[] { + return [ + // 체크박스 선택 + { + id: "select", + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + className="translate-y-0.5" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + aria-label="Select row" + className="translate-y-0.5" + /> + ), + size: 40, + enableSorting: false, + enableHiding: false, + }, + + // 문서번호 + 우선순위 + { + accessorKey: "docNumber", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const doc = row.original + return ( +
{/* ✅ items-start 추가 */} + {doc.docNumber} + {/* {doc.currentStagePriority && ( + + {getPriorityText(doc.currentStagePriority)} + + )} */} +
+ ) + }, + size: 120, + enableResizing: true, + meta: { + excelHeader: "문서번호" + }, + }, + + // 문서명 + 담당자 + { + accessorKey: "title", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const doc = row.original + return ( +
+
+ {doc.title} +
+
+ {doc.pic && ( + + PIC: {doc.pic} + + )} + {doc.currentStageAssigneeName && ( +
+ + {doc.currentStageAssigneeName} +
+ )} +
+
+ ) + }, + size: 250, + enableResizing: true, + meta: { + excelHeader: "문서명" + }, + }, + + // 현재 스테이지 + { + accessorKey: "currentStageName", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const doc = row.original + if (!doc.currentStageName) return - + + return ( +
+ {doc.currentStageName} + + {getStatusText(doc.currentStageStatus || '')} + +
+ ) + }, + size: 140, + enableResizing: true, + meta: { + excelHeader: "현재 스테이지" + }, + }, + + // 일정 정보 + { + accessorKey: "currentStagePlanDate", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const doc = row.original + if (!doc.currentStagePlanDate) return - + + return ( +
+
+ 계획: + {formatDate(doc.currentStagePlanDate)} +
+ {doc.currentStageActualDate && ( +
+ 실제: + {formatDate(doc.currentStageActualDate)} +
+ )} + +
+ ) + }, + size: 140, + enableResizing: true, + meta: { + excelHeader: "계획일" + }, + }, + + // 진행률 + { + accessorKey: "progressPercentage", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const doc = row.original + const progress = doc.progressPercentage || 0 + const completed = doc.completedStages || 0 + const total = doc.totalStages || 0 + + return ( +
+
+ + + {progress}% + +
+ + {completed} / {total} 스테이지 + +
+ ) + }, + size: 120, + enableResizing: true, + meta: { + excelHeader: "진행률" + }, + }, + + // 최신 리비전 + { + accessorKey: "latestRevision", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const doc = row.original + if (!doc.latestRevision) return 없음 + + return ( +
+ {doc.latestRevision} + {/*
{doc.latestRevisionUploaderName}
*/} + {doc.latestRevisionStatus && ( + + {getStatusText(doc.latestRevisionStatus)} + + )} + {doc.latestSubmittedDate && ( +
+ {formatDate(doc.latestSubmittedDate)} +
+ )} +
+ ) + }, + size: 140, + enableResizing: true, + meta: { + excelHeader: "최신 리비전" + }, + }, + + // 업데이트 일시 + { + accessorKey: "updatedAt", + header: ({ column }) => ( + + ), + cell: ({ cell }) => ( + + {formatDateTime(cell.getValue() as Date)} + + ), + size: 140, + enableResizing: true, + meta: { + excelHeader: "업데이트" + }, + }, + + // 액션 메뉴 + // 액션 메뉴 + { + id: "actions", + enableHiding: false, + cell: function Cell({ row }) { + const doc = row.original + const canSubmit = doc.currentStageStatus === 'IN_PROGRESS' + const canApprove = doc.currentStageStatus === 'SUBMITTED' + const isPlantProject = projectType === "plant" + + // 메뉴 아이템들을 그룹별로 정의 + const viewActions = [ + { + key: "view", + label: "상세보기", + icon: Eye, + action: () => setRowAction({ row, type: "view" }), + show: true + } + ] + + const editActions = [ + { + key: "update", + label: "편집", + icon: Edit, + action: () => setRowAction({ row, type: "update" }), + show: isPlantProject + } + ] + + const fileActions = [ + { + key: "upload", + label: "리비전 업로드", + icon: Upload, + action: () => setRowAction({ row, type: "upload" }), + show: canSubmit + } + ] + + const dangerActions = [ + { + key: "delete", + label: "삭제", + icon: Trash2, + action: () => setRowAction({ row, type: "delete" }), + show: isPlantProject, + className: "text-red-600", + shortcut: "⌘⌫" + } + ] + + // 각 그룹에서 표시될 아이템이 있는지 확인 + const hasEditActions = editActions.some(action => action.show) + const hasFileActions = fileActions.some(action => action.show) + const hasDangerActions = dangerActions.some(action => action.show) + + return ( + + + + + + {/* 기본 액션 그룹 */} + {viewActions.map(action => action.show && ( + + + {action.label} + {action.shortcut && ( + {action.shortcut} + )} + + ))} + + {/* 편집 액션 그룹 */} + {hasEditActions && ( + <> + + {editActions.map(action => action.show && ( + + + {action.label} + {action.shortcut && ( + {action.shortcut} + )} + + ))} + + )} + + {/* 파일 액션 그룹 */} + {hasFileActions && ( + <> + + {fileActions.map(action => action.show && ( + + + {action.label} + {action.shortcut && ( + {action.shortcut} + )} + + ))} + + )} + + {/* 위험한 액션 그룹 */} + {hasDangerActions && ( + <> + + {dangerActions.map(action => action.show && ( + + + {action.label} + {action.shortcut && ( + {action.shortcut} + )} + + ))} + + )} + + + ) + }, + size: 40, + } + ] +} + +// 확장된 행 컨텐츠 컴포넌트 (업데이트된 버전) +export const UpdatedExpandedRowContent = ({ + document +}: { + document: EnhancedDocumentsView +}) => { + if (!document.allStages || document.allStages.length === 0) { + return ( +
+ 스테이지 정보가 없습니다. +
+ ) + } + + return ( +
+

+ + 전체 스테이지 현황 +

+ +
+ {document.allStages.map((stage, index) => ( +
+
+
+
+ {stage.stageOrder || index + 1} +
+
+
+ +
+
{stage.stageName}
+ {stage.assigneeName && ( +
+ + {stage.assigneeName} +
+ )} +
+
+ +
+
+ 계획: + {formatDate(stage.planDate)} +
+ {stage.actualDate && ( +
+ 완료: + {formatDate(stage.actualDate)} +
+ )} + +
+ + {getPriorityText(stage.priority)} + + + {getStatusText(stage.stageStatus)} + +
+
+
+ ))} +
+
+ ) +} \ No newline at end of file -- cgit v1.2.3