diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-13 07:11:18 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-13 07:11:18 +0000 |
| commit | 0fddf148402fd6b99a1b3800d73679899bcb2ed3 (patch) | |
| tree | eb51c02e6fa6037ddcc38a3b57d10d8c739125cf /lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx | |
| parent | c72d0897f7b37843109c86f61d97eba05ba3ca0d (diff) | |
(대표님) 20250613 16시 10분 global css, b-rfq, document 등
Diffstat (limited to 'lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx')
| -rw-r--r-- | lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx | 616 |
1 files changed, 326 insertions, 290 deletions
diff --git a/lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx b/lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx index b80c0869..ad184378 100644 --- a/lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx +++ b/lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx @@ -16,123 +16,36 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Button } from "@/components/ui/button" -import { Badge } from "@/components/ui/badge" import { Ellipsis, - Calendar, - CalendarClock, - User, FileText, Eye, Edit, Trash2, - Building, - Code, - Settings } from "lucide-react" import { cn } from "@/lib/utils" import { SimplifiedDocumentsView } from "@/db/schema" +// DocumentSelectionContext를 import (실제 파일 경로에 맞게 수정 필요) +// 예: import { DocumentSelectionContext } from "../user-vendor-document-display" +// 또는: import { DocumentSelectionContext } from "./user-vendor-document-display" +import { DocumentSelectionContext } from "@/components/ship-vendor-document/user-vendor-document-table-container" + interface GetColumnsProps { setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<SimplifiedDocumentsView> | null>> } -// 유틸리티 함수들 -const getDrawingKindText = (drawingKind: string) => { - switch (drawingKind) { - case 'B3': return 'B3 도면' - case 'B4': return 'B4 도면' - case 'B5': return 'B5 도면' - default: return drawingKind - } -} - -const getDrawingKindColor = (drawingKind: string) => { - switch (drawingKind) { - case 'B3': return 'bg-blue-100 text-blue-800' - case 'B4': return 'bg-green-100 text-green-800' - case 'B5': return 'bg-purple-100 text-purple-800' - default: return 'bg-gray-100 text-gray-800' - } -} - -// 스테이지별 이름 표시 컴포넌트 -const StageNameDisplay = ({ - stageName, - drawingKind, - isFirst = true -}: { - stageName: string | null, - drawingKind: string | null, - isFirst?: boolean -}) => { - if (!stageName) return <span className="text-gray-400">-</span> - - const stageType = isFirst ? "1차" : "2차" - const getExpectedStage = () => { - if (drawingKind === 'B4') return isFirst ? 'Pre' : 'Work' - if (drawingKind === 'B3') return isFirst ? 'Approval' : 'Work' - if (drawingKind === 'B5') return isFirst ? 'First' : 'Second' - return '' - } - - return ( - <div className="flex flex-col gap-1"> - <div className="text-xs text-gray-500">{stageType} 스테이지</div> - <div className="text-sm font-medium">{stageName}</div> - {getExpectedStage() && ( - <div className="text-xs text-gray-400">({getExpectedStage()})</div> - )} - </div> - ) -} - -// 날짜 정보 표시 컴포넌트 -const StageDateInfo = ({ - planDate, - actualDate, - stageName -}: { - planDate: string | null - actualDate: string | null - stageName: string | null -}) => { - if (!planDate && !actualDate) { - return <span className="text-gray-400">날짜 미설정</span> - } - - const isCompleted = !!actualDate - const isLate = actualDate && planDate && new Date(actualDate) > new Date(planDate) - +// 날짜 표시 컴포넌트 (간단 버전) +const DateDisplay = ({ date, isSelected = false }: { date: string | null, isSelected?: boolean }) => { + if (!date) return <span className="text-gray-400">-</span> + return ( - <div className="flex flex-col gap-1"> - {planDate && ( - <div className="text-sm"> - <span className="text-gray-500">계획: </span> - <span>{formatDate(planDate)}</span> - </div> - )} - {actualDate && ( - <div className="text-sm"> - <span className="text-gray-500">실제: </span> - <span className={cn( - isLate ? "text-red-600 font-medium" : "text-green-600 font-medium" - )}> - {formatDate(actualDate)} - </span> - </div> - )} - {!actualDate && planDate && ( - <div className="text-xs text-orange-600"> - 진행중 - </div> - )} - {isCompleted && ( - <div className="text-xs text-green-600"> - ✓ 완료 - </div> - )} - </div> + <span className={cn( + "text-sm", + isSelected && "text-blue-600 font-semibold" + )}> + {formatDate(date)} + </span> ) } @@ -140,36 +53,28 @@ export function getSimplifiedDocumentColumns({ setRowAction, }: GetColumnsProps): ColumnDef<SimplifiedDocumentsView>[] { - // 기본 컬럼들 - const baseColumns: ColumnDef<SimplifiedDocumentsView>[] = [ - // 체크박스 선택 + const columns: ColumnDef<SimplifiedDocumentsView>[] = [ + // 라디오 버튼 같은 체크박스 선택 { id: "select", header: ({ table }) => ( - <Checkbox - checked={ - table.getIsAllPageRowsSelected() || - (table.getIsSomePageRowsSelected() && "indeterminate") - } - onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - className="translate-y-0.5" - /> - ), - cell: ({ row }) => ( - <Checkbox - checked={row.getIsSelected()} - onCheckedChange={(value) => row.toggleSelected(!!value)} - aria-label="Select row" - className="translate-y-0.5" - /> + <div className="flex items-center justify-center"> + <span className="text-xs text-gray-500">선택</span> + </div> ), + cell: ({ row }) => { + const doc = row.original + + return ( + <SelectCell documentId={doc.documentId} /> + ) + }, size: 40, enableSorting: false, enableHiding: false, }, - // 문서번호 + Drawing Kind + // 문서번호 (선택된 행 하이라이트 적용) { accessorKey: "docNumber", header: ({ column }) => ( @@ -177,33 +82,19 @@ export function getSimplifiedDocumentColumns({ ), cell: ({ row }) => { const doc = row.original + return ( - <div className="flex flex-col gap-1 items-start"> - <span className="font-mono text-sm font-medium">{doc.docNumber}</span> - {doc.vendorDocNumber && ( - <span className="font-mono text-xs text-gray-500"> - 벤더: {doc.vendorDocNumber} - </span> - )} - {doc.drawingKind && ( - <Badge - variant="outline" - className={cn("text-xs", getDrawingKindColor(doc.drawingKind))} - > - {getDrawingKindText(doc.drawingKind)} - </Badge> - )} - </div> + <DocNumberCell doc={doc} /> ) }, - size: 140, + size: 120, enableResizing: true, meta: { excelHeader: "문서번호" }, }, - // 문서명 + 프로젝트/벤더 정보 + // 문서명 (선택된 행 하이라이트 적용) { accessorKey: "title", header: ({ column }) => ( @@ -211,148 +102,136 @@ export function getSimplifiedDocumentColumns({ ), cell: ({ row }) => { const doc = row.original + return ( - <div className="min-w-0 flex-1"> - <div className="font-medium text-gray-900 truncate" title={doc.title}> - {doc.title} - </div> - <div className="flex items-center gap-2 text-sm text-gray-500 mt-1"> - {doc.pic && ( - <span className="text-xs bg-gray-100 px-2 py-0.5 rounded"> - PIC: {doc.pic} - </span> - )} - {doc.projectCode && ( - <div className="flex items-center gap-1"> - <Building className="w-3 h-3" /> - <span>{doc.projectCode}</span> - </div> - )} - {doc.vendorName && ( - <div className="flex items-center gap-1"> - <Code className="w-3 h-3" /> - <span className="truncate max-w-[100px]">{doc.vendorName}</span> - </div> - )} - </div> - </div> + <TitleCell doc={doc} /> ) }, - size: 200, enableResizing: true, meta: { excelHeader: "문서명" }, }, - // 첫 번째 스테이지 정보 + // 프로젝트 코드 { - accessorKey: "firstStageName", + accessorKey: "projectCode", header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="1차 스테이지" /> + <DataTableColumnHeaderSimple column={column} title="프로젝트" /> ), cell: ({ row }) => { - const doc = row.original + const projectCode = row.original.projectCode + return ( - <StageNameDisplay - stageName={doc.firstStageName} - drawingKind={doc.drawingKind} - isFirst={true} - /> + <ProjectCodeCell projectCode={projectCode} documentId={row.original.documentId} /> ) }, - size: 130, enableResizing: true, meta: { - excelHeader: "1차 스테이지" + excelHeader: "프로젝트" }, }, - // 첫 번째 스테이지 날짜 + // 1차 스테이지 그룹 { - accessorKey: "firstStagePlanDate", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="1차 일정" /> - ), - cell: ({ row }) => { - const doc = row.original + id: "firstStageGroup", + header: ({ table }) => { + // 첫 번째 행의 firstStageName을 그룹 헤더로 사용 + const firstRow = table.getRowModel().rows[0]?.original + const stageName = firstRow?.firstStageName || "1차 스테이지" return ( - <StageDateInfo - planDate={doc.firstStagePlanDate} - actualDate={doc.firstStageActualDate} - stageName={doc.firstStageName} - /> - ) - }, - size: 140, - enableResizing: true, - meta: { - excelHeader: "1차 일정" - }, - }, - - // 두 번째 스테이지 정보 - { - accessorKey: "secondStageName", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="2차 스테이지" /> - ), - cell: ({ row }) => { - const doc = row.original - return ( - <StageNameDisplay - stageName={doc.secondStageName} - drawingKind={doc.drawingKind} - isFirst={false} - /> + <div className="text-center font-medium text-gray-700"> + {stageName} + </div> ) }, - size: 130, - enableResizing: true, - meta: { - excelHeader: "2차 스테이지" - }, + columns: [ + { + accessorKey: "firstStagePlanDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="계획일" /> + ), + cell: ({ row }) => { + return <FirstStagePlanDateCell row={row} /> + }, + enableResizing: true, + meta: { + excelHeader: "1차 계획일" + }, + }, + { + accessorKey: "firstStageActualDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="실제일" /> + ), + cell: ({ row }) => { + return <FirstStageActualDateCell row={row} /> + }, + enableResizing: true, + meta: { + excelHeader: "1차 실제일" + }, + }, + ], }, - // 두 번째 스테이지 날짜 + // 2차 스테이지 그룹 { - accessorKey: "secondStagePlanDate", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="2차 일정" /> - ), - cell: ({ row }) => { - const doc = row.original + id: "secondStageGroup", + header: ({ table }) => { + // 첫 번째 행의 secondStageName을 그룹 헤더로 사용 + const firstRow = table.getRowModel().rows[0]?.original + const stageName = firstRow?.secondStageName || "2차 스테이지" return ( - <StageDateInfo - planDate={doc.secondStagePlanDate} - actualDate={doc.secondStageActualDate} - stageName={doc.secondStageName} - /> + <div className="text-center font-medium text-gray-700"> + {stageName} + </div> ) }, - size: 140, - enableResizing: true, - meta: { - excelHeader: "2차 일정" - }, + columns: [ + { + accessorKey: "secondStagePlanDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="계획일" /> + ), + cell: ({ row }) => { + return <SecondStagePlanDateCell row={row} /> + }, + enableResizing: true, + meta: { + excelHeader: "2차 계획일" + }, + }, + { + accessorKey: "secondStageActualDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="실제일" /> + ), + cell: ({ row }) => { + return <SecondStageActualDateCell row={row} /> + }, + enableResizing: true, + meta: { + excelHeader: "2차 실제일" + }, + }, + ], }, // 첨부파일 수 { accessorKey: "attachmentCount", header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="첨부파일" /> + <DataTableColumnHeaderSimple column={column} title="파일" /> ), cell: ({ row }) => { const count = row.original.attachmentCount || 0 + return ( - <div className="flex items-center gap-1"> - <FileText className="w-4 h-4 text-gray-400" /> - <span className="text-sm font-medium">{count}</span> - </div> + <AttachmentCountCell count={count} documentId={row.original.documentId} /> ) }, - size: 80, + size: 60, enableResizing: true, meta: { excelHeader: "첨부파일" @@ -365,12 +244,11 @@ export function getSimplifiedDocumentColumns({ header: ({ column }) => ( <DataTableColumnHeaderSimple column={column} title="업데이트" /> ), - cell: ({ cell }) => ( - <span className="text-sm text-gray-600"> - {formatDateTime(cell.getValue() as Date)} - </span> - ), - size: 140, + cell: ({ cell, row }) => { + return ( + <UpdatedAtCell updatedAt={cell.getValue() as Date} documentId={row.original.documentId} /> + ) + }, enableResizing: true, meta: { excelHeader: "업데이트" @@ -378,50 +256,208 @@ export function getSimplifiedDocumentColumns({ }, // 액션 버튼 - { - id: "actions", - header: () => <span className="sr-only">Actions</span>, - cell: ({ row }) => { - const doc = row.original - return ( - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button variant="ghost" className="h-8 w-8 p-0"> - <span className="sr-only">Open menu</span> - <Ellipsis className="h-4 w-4" /> - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent align="end"> - <DropdownMenuItem - onClick={() => setRowAction({ type: "view", row: doc })} - > - <Eye className="mr-2 h-4 w-4" /> - 보기 - </DropdownMenuItem> - <DropdownMenuItem - onClick={() => setRowAction({ type: "edit", row: doc })} - > - <Edit className="mr-2 h-4 w-4" /> - 편집 - </DropdownMenuItem> - <DropdownMenuSeparator /> - <DropdownMenuItem - onClick={() => setRowAction({ type: "delete", row: doc })} - className="text-red-600" - > - <Trash2 className="mr-2 h-4 w-4" /> - 삭제 - <DropdownMenuShortcut>⌫</DropdownMenuShortcut> - </DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - ) - }, - size: 50, - enableSorting: false, - enableHiding: false, - }, + // { + // id: "actions", + // header: () => <span className="sr-only">Actions</span>, + // cell: ({ row }) => { + // const doc = row.original + // return ( + // <DropdownMenu> + // <DropdownMenuTrigger asChild> + // <Button variant="ghost" className="h-8 w-8 p-0"> + // <span className="sr-only">Open menu</span> + // <Ellipsis className="h-4 w-4" /> + // </Button> + // </DropdownMenuTrigger> + // <DropdownMenuContent align="end"> + // <DropdownMenuItem + // onClick={() => setRowAction({ type: "view", row: doc })} + // > + // <Eye className="mr-2 h-4 w-4" /> + // 보기 + // </DropdownMenuItem> + // <DropdownMenuItem + // onClick={() => setRowAction({ type: "edit", row: doc })} + // > + // <Edit className="mr-2 h-4 w-4" /> + // 편집 + // </DropdownMenuItem> + // <DropdownMenuSeparator /> + // <DropdownMenuItem + // onClick={() => setRowAction({ type: "delete", row: doc })} + // className="text-red-600" + // > + // <Trash2 className="mr-2 h-4 w-4" /> + // 삭제 + // <DropdownMenuShortcut>⌫</DropdownMenuShortcut> + // </DropdownMenuItem> + // </DropdownMenuContent> + // </DropdownMenu> + // ) + // }, + // size: 50, + // enableSorting: false, + // enableHiding: false, + // }, ] - return baseColumns + return columns +} + +// 개별 셀 컴포넌트들 (Context 사용) +function SelectCell({ documentId }: { documentId: number }) { + const { selectedDocumentId, setSelectedDocumentId } = React.useContext(DocumentSelectionContext); + const isSelected = selectedDocumentId === documentId; + + return ( + <div className="flex items-center justify-center"> + <input + type="radio" + checked={isSelected} + onChange={() => { + const newSelection = isSelected ? null : documentId; + setSelectedDocumentId(newSelection); + }} + className="cursor-pointer w-4 h-4" + /> + </div> + ); +} + +function DocNumberCell({ doc }: { doc: SimplifiedDocumentsView }) { + const { selectedDocumentId, setSelectedDocumentId } = React.useContext(DocumentSelectionContext); + const isSelected = selectedDocumentId === doc.documentId; + + return ( + <div + className={cn( + "font-mono text-sm font-medium cursor-pointer px-2 py-1 rounded transition-colors", + isSelected + ? "text-blue-600 font-bold bg-blue-50" + : "hover:bg-gray-50" + )} + onClick={() => { + const newSelection = isSelected ? null : doc.documentId; + setSelectedDocumentId(newSelection); + }} + > + {doc.docNumber} + </div> + ); +} + +function TitleCell({ doc }: { doc: SimplifiedDocumentsView }) { + const { selectedDocumentId, setSelectedDocumentId } = React.useContext(DocumentSelectionContext); + const isSelected = selectedDocumentId === doc.documentId; + + return ( + <div + className={cn( + "font-medium text-gray-900 truncate max-w-[300px] cursor-pointer px-2 py-1 rounded transition-colors", + isSelected + ? "text-blue-600 font-bold bg-blue-50" + : "hover:bg-gray-50" + )} + title={doc.title} + onClick={() => { + const newSelection = isSelected ? null : doc.documentId; + setSelectedDocumentId(newSelection); + }} + > + {doc.title} + </div> + ); +} + +function ProjectCodeCell({ projectCode, documentId }: { projectCode: string | null, documentId: number }) { + const { selectedDocumentId } = React.useContext(DocumentSelectionContext); + const isSelected = selectedDocumentId === documentId; + + if (!projectCode) return <span className="text-gray-400">-</span>; + + return ( + <span className={cn( + "text-sm font-medium", + isSelected && "text-blue-600 font-bold" + )}> + {projectCode} + </span> + ); +} + +function FirstStagePlanDateCell({ row }: { row: any }) { + const { selectedDocumentId } = React.useContext(DocumentSelectionContext); + const isSelected = selectedDocumentId === row.original.documentId; + + return <DateDisplay date={row.original.firstStagePlanDate} isSelected={isSelected} />; +} + +function FirstStageActualDateCell({ row }: { row: any }) { + const { selectedDocumentId } = React.useContext(DocumentSelectionContext); + const isSelected = selectedDocumentId === row.original.documentId; + const date = row.original.firstStageActualDate; + + return ( + <div className={cn( + date ? "text-green-600 font-medium" : "", + isSelected && date && "text-green-700 font-bold" + )}> + <DateDisplay date={date} isSelected={isSelected && !date} /> + {date && <span className="text-xs block">✓ 완료</span>} + </div> + ); +} + +function SecondStagePlanDateCell({ row }: { row: any }) { + const { selectedDocumentId } = React.useContext(DocumentSelectionContext); + const isSelected = selectedDocumentId === row.original.documentId; + + return <DateDisplay date={row.original.secondStagePlanDate} isSelected={isSelected} />; +} + +function SecondStageActualDateCell({ row }: { row: any }) { + const { selectedDocumentId } = React.useContext(DocumentSelectionContext); + const isSelected = selectedDocumentId === row.original.documentId; + const date = row.original.secondStageActualDate; + + return ( + <div className={cn( + date ? "text-green-600 font-medium" : "", + isSelected && date && "text-green-700 font-bold" + )}> + <DateDisplay date={date} isSelected={isSelected && !date} /> + {date && <span className="text-xs block">✓ 완료</span>} + </div> + ); +} + +function AttachmentCountCell({ count, documentId }: { count: number, documentId: number }) { + const { selectedDocumentId } = React.useContext(DocumentSelectionContext); + const isSelected = selectedDocumentId === documentId; + + return ( + <div className="flex items-center justify-center gap-1"> + <FileText className="w-4 h-4 text-gray-400" /> + <span className={cn( + "text-sm font-medium", + isSelected && "text-blue-600 font-bold" + )}> + {count} + </span> + </div> + ); +} + +function UpdatedAtCell({ updatedAt, documentId }: { updatedAt: Date, documentId: number }) { + const { selectedDocumentId } = React.useContext(DocumentSelectionContext); + const isSelected = selectedDocumentId === documentId; + + return ( + <span className={cn( + "text-sm text-gray-600", + isSelected && "text-blue-600 font-semibold" + )}> + {formatDateTime(updatedAt)} + </span> + ); }
\ No newline at end of file |
