From 0fddf148402fd6b99a1b3800d73679899bcb2ed3 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Fri, 13 Jun 2025 07:11:18 +0000
Subject: (대표님) 20250613 16시 10분 global css, b-rfq, document 등
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../ship/enhanced-doc-table-columns.tsx            | 616 +++++++++++----------
 1 file changed, 326 insertions(+), 290 deletions(-)

(limited to 'lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx')

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
-- 
cgit v1.2.3

