From b8e8328b1ffffb80bf4ebb776a4a24e5680fc5bc Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Fri, 28 Mar 2025 00:42:08 +0000
Subject: 테이블 칼럼 리사이즈 및 핀 충돌 해결
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/po/table/po-table-columns.tsx | 300 ++++++++++++++++++++++++--------------
 1 file changed, 187 insertions(+), 113 deletions(-)

(limited to 'lib/po/table/po-table-columns.tsx')

diff --git a/lib/po/table/po-table-columns.tsx b/lib/po/table/po-table-columns.tsx
index a13b2acf..6517b9b3 100644
--- a/lib/po/table/po-table-columns.tsx
+++ b/lib/po/table/po-table-columns.tsx
@@ -3,7 +3,7 @@
 import * as React from "react"
 import { type DataTableRowAction } from "@/types/table"
 import { type ColumnDef } from "@tanstack/react-table"
-import { InfoIcon, PenIcon } from "lucide-react"
+import { InfoIcon, FileTextIcon, SendIcon, FileSignatureIcon, MoreHorizontalIcon, ExternalLinkIcon } from "lucide-react"
 
 import { formatDate } from "@/lib/utils"
 import { Button } from "@/components/ui/button"
@@ -13,19 +13,28 @@ import {
   TooltipProvider,
   TooltipTrigger,
 } from "@/components/ui/tooltip"
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuLabel,
+  DropdownMenuSeparator,
+  DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
 
+import { Badge } from "@/components/ui/badge"
 import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
 import { poColumnsConfig } from "@/config/poColumnsConfig"
-import { ContractDetail } from "@/db/schema/contract"
+import { ContractDetailParsed } from "@/db/schema/contract"
 
 interface GetColumnsProps {
-  setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<ContractDetail> | null>>
+  setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<ContractDetailParsed> | null>>
 }
 
 /**
  * tanstack table column definitions with nested headers
  */
-export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<ContractDetail>[] {
+export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<ContractDetailParsed>[] {
   // ----------------------------------------------------------------
   // 1) select column (checkbox) - if needed
   // ----------------------------------------------------------------
@@ -33,164 +42,229 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<Contrac
   // ----------------------------------------------------------------
   // 2) actions column (buttons for item info and signature request)
   // ----------------------------------------------------------------
-  const actionsColumn: ColumnDef<ContractDetail> = {
+  const actionsColumn: ColumnDef<ContractDetailParsed> = {
     id: "actions",
     enableHiding: false,
+    header: () => <div className="text-center">Actions</div>,
     cell: function Cell({ row }) {
       // Check if this contract already has a signature envelope
       const hasSignature = row.original.hasSignature;
-      
+      const contract = row.original;
+
       return (
-        <div className="flex items-center space-x-1">
-          {/* Item Info Button */}
+        <div className="flex items-center justify-center gap-2">
+          {/* Items Button - Visually distinct with badge showing count */}
           <TooltipProvider>
             <Tooltip>
               <TooltipTrigger asChild>
                 <Button
-                  variant="ghost"
-                  size="icon"
+                  variant="outline"
+                  size="sm"
+                  className="flex items-center gap-1 h-8 px-2"
                   onClick={() => setRowAction({ row, type: "items" })}
                 >
-                  <InfoIcon className="h-4 w-4" aria-hidden="true" />
+                  <FileTextIcon className="h-3.5 w-3.5" aria-hidden="true" />
+                  Items
+                  {contract.items && contract.items.length > 0 && (
+                    <Badge variant="secondary" className="ml-1 text-xs h-5 px-1.5">
+                      {contract.items.length}
+                    </Badge>
+                  )}
                 </Button>
               </TooltipTrigger>
               <TooltipContent>
-                View Item Info
+                View contract items and details
               </TooltipContent>
             </Tooltip>
           </TooltipProvider>
-          
-          {/* Signature Request Button - only show if no signature exists */}
-          {!hasSignature && (
+
+          {/* Signature related actions */}
+          {hasSignature ? (
+            <TooltipProvider>
+              <Tooltip>
+                <TooltipTrigger asChild>
+                  <Button
+                    variant="outline"
+                    size="sm"
+                    className="h-8 px-2"
+                    onClick={() => setRowAction({ row, type: "esign-detail" })}
+                  >
+                    <FileSignatureIcon className="h-3.5 w-3.5 mr-1" aria-hidden="true" />
+                    View Signatures
+                  </Button>
+                </TooltipTrigger>
+                <TooltipContent>
+                  View signature status and details
+                </TooltipContent>
+              </Tooltip>
+            </TooltipProvider>
+          ) : (
             <TooltipProvider>
               <Tooltip>
                 <TooltipTrigger asChild>
                   <Button
-                    variant="ghost"
-                    size="icon"
+                    variant="outline"
+                    size="sm"
+                    className="h-8 px-2 text-blue-600 border-blue-200 hover:bg-blue-50"
                     onClick={() => setRowAction({ row, type: "signature" })}
                   >
-                    <PenIcon className="h-4 w-4" aria-hidden="true" />
+                    <SendIcon className="h-3.5 w-3.5 mr-1" aria-hidden="true" />
+                    Request Signatures
                   </Button>
                 </TooltipTrigger>
                 <TooltipContent>
-                  Request Electronic Signature
+                  Send electronic signature requests
                 </TooltipContent>
               </Tooltip>
             </TooltipProvider>
           )}
+
+          {/* Alternative: Dropdown menu for more actions */}
+          {/* 
+          <DropdownMenu>
+            <DropdownMenuTrigger asChild>
+              <Button variant="ghost" size="icon">
+                <MoreHorizontalIcon className="h-4 w-4" />
+              </Button>
+            </DropdownMenuTrigger>
+            <DropdownMenuContent align="end">
+              <DropdownMenuLabel>Actions</DropdownMenuLabel>
+              <DropdownMenuItem onClick={() => setRowAction({ row, type: "items" })}>
+                <FileTextIcon className="h-4 w-4 mr-2" />
+                View Items
+              </DropdownMenuItem>
+              <DropdownMenuSeparator />
+              {hasSignature ? (
+                <DropdownMenuItem onClick={() => setRowAction({ row, type: "esign-detail" })}>
+                  <FileSignatureIcon className="h-4 w-4 mr-2" />
+                  View Signatures
+                </DropdownMenuItem>
+              ) : (
+                <DropdownMenuItem onClick={() => setRowAction({ row, type: "signature" })}>
+                  <SendIcon className="h-4 w-4 mr-2" />
+                  Request Signatures
+                </DropdownMenuItem>
+              )}
+            </DropdownMenuContent>
+          </DropdownMenu>
+          */}
         </div>
       );
     },
-    size: 80, // Increased width to accommodate both buttons
+    size: 340, // Adjusted for multiple buttons
+    minSize:280
   };
 
   // ----------------------------------------------------------------
   // 3) Regular columns grouped by group name
   // ----------------------------------------------------------------
-  // 3-1) groupMap: { [groupName]: ColumnDef<ContractDetail>[] }
-  const groupMap: Record<string, ColumnDef<ContractDetail>[]> = {};
-
- // (1) JSON config를 읽어서 ColumnDef를 생성하는 부분 (일부 발췌)
-poColumnsConfig.forEach((cfg) => {
-  const groupName = cfg.group || "_noGroup"
-  if (!groupMap[groupName]) {
-    groupMap[groupName] = []
-  }
-
-  let childCol: ColumnDef<ContractDetail>
-
-  if (cfg.type === "custom" && cfg.customType === "esignStatus") {
-    // ========================================
-    // (2) 전자서명 전용 커스텀 컬럼
-    // ========================================
-    childCol = {
-      id: cfg.id,
-      header: ({ column }) => (
-        <DataTableColumnHeaderSimple column={column} title={cfg.label} />
-      ),
-      // 여기서 row.original.envelopes 등 활용하여 최신 전자서명 상태 표시
-      cell: ({ row }) => {
-        const data = row.original
-        if (!data.envelopes || data.envelopes.length === 0) {
+  // 3-1) groupMap: { [groupName]: ColumnDef<ContractDetailParsed>[] }
+  const groupMap: Record<string, ColumnDef<ContractDetailParsed>[]> = {};
+
+  // (1) JSON config를 읽어서 ColumnDef를 생성하는 부분 (일부 발췌)
+  poColumnsConfig.forEach((cfg) => {
+    const groupName = cfg.group || "_noGroup"
+    if (!groupMap[groupName]) {
+      groupMap[groupName] = []
+    }
+
+    let childCol: ColumnDef<ContractDetailParsed>
+
+    if (cfg.type === "custom") {
+      // ========================================
+      // (2) 전자서명 전용 커스텀 컬럼
+      // ========================================
+      childCol = {
+        id: cfg.id,
+        header: ({ column }) => (
+          <DataTableColumnHeaderSimple column={column} title={cfg.label} />
+        ),
+        // 여기서 row.original.envelopes 등 활용하여 최신 전자서명 상태 표시
+        cell: ({ row }) => {
+          const data = row.original
+          if (!data.envelopes || data.envelopes.length === 0) {
+            return (
+              <div className="text-sm text-gray-500">
+                No E-Sign
+              </div>
+            )
+          }
+
+          // envelopes가 여러 개 있으면 최신(가장 최근 updatedAt) 가져오기
+          const sorted = [...data.envelopes].sort((a, b) => {
+            const dateA = new Date(a.updatedAt)
+            const dateB = new Date(b.updatedAt)
+            return dateB.getTime() - dateA.getTime()
+          })
+          const latest = sorted[0]
+
+          const status = latest.envelopeStatus;
+
+          if (!status) {
+            // status가 null이면, 기본 색상이나 별도 표시
+            return <span className="text-gray-500">No Status</span>
+          }
+          
+          const colorMap: Record<string, string> = {
+            completed: "text-green-600",
+            sent: "text-blue-600",
+            voided: "text-red-600",
+          };
+          
+          const colorClass = colorMap[status] ?? "text-gray-700";
+          
           return (
-            <div className="text-sm text-gray-500">
-              No E-Sign
+            <div className="flex items-center">
+              <span
+                onClick={() => setRowAction({ row, type: "esign-detail" })}
+                className={`${colorClass} cursor-pointer flex items-center`}
+              >
+                {status}
+                <ExternalLinkIcon className="ml-1 h-3 w-3" />
+              </span>
             </div>
           )
-        }
-
-        // envelopes가 여러 개 있으면 최신(가장 최근 updatedAt) 가져오기
-        const sorted = [...data.envelopes].sort((a, b) => {
-          const dateA = new Date(a.updatedAt)
-          const dateB = new Date(b.updatedAt)
-          return dateB.getTime() - dateA.getTime()
-        })
-        const latest = sorted[0]
-
-        // 상태에 따라 다른 UI 색상/아이콘
-        const status = latest.envelopeStatus // "sent", "completed", ...
-        const colorMap: Record<string, string> = {
-          completed: "text-green-600",
-          sent: "text-blue-600",
-          voided: "text-red-600",
+        },
+        meta: {
+          excelHeader: cfg.excelHeader,
+          group: cfg.group,
+          type: cfg.type,
+        },
+      }
+    } else {
+      // ========================================
+      // (3) 일반 컬럼 (type: text/date/number 등)
+      // ========================================
+      childCol = {
+        accessorKey: cfg.id,
+        enableResizing: true,
+        header: ({ column }) => (
+          <DataTableColumnHeaderSimple column={column} title={cfg.label} />
+        ),
+        meta: {
+          excelHeader: cfg.excelHeader,
+          group: cfg.group,
+          type: cfg.type,
+        },
+        cell: ({ row, cell }) => {
+          // 날짜 포맷, 숫자 포맷 등 처리
+          if (cfg.type === "date") {
+            const dateVal = cell.getValue() as Date
+            return formatDate(dateVal)
+          }
           // ...
-        }
-        const colorClass = colorMap[status] || "text-gray-700"
-
-        return (
-          <Button
-            onClick={() => {
-              // 다이얼로그 열기 등
-              // 예: setRowAction({ row, type: "esign-detail" })
-              setRowAction({ row, type: "esign-detail" })
-            }}
-            className={`underline underline-offset-2 ${colorClass}`}
-          >
-            {status}
-          </Button>
-        )
-      },
-      meta: {
-        excelHeader: cfg.excelHeader,
-        group: cfg.group,
-        type: cfg.type,
-      },
-    }
-  } else {
-    // ========================================
-    // (3) 일반 컬럼 (type: text/date/number 등)
-    // ========================================
-    childCol = {
-      accessorKey: cfg.id,
-      enableResizing: true,
-      header: ({ column }) => (
-        <DataTableColumnHeaderSimple column={column} title={cfg.label} />
-      ),
-      meta: {
-        excelHeader: cfg.excelHeader,
-        group: cfg.group,
-        type: cfg.type,
-      },
-      cell: ({ row, cell }) => {
-        // 날짜 포맷, 숫자 포맷 등 처리
-        if (cfg.type === "date") {
-          const dateVal = cell.getValue() as Date
-          return formatDate(dateVal)
-        }
-        // ...
-        return row.getValue(cfg.id) ?? ""
-      },
+          return row.getValue(cfg.id) ?? ""
+        },
+      }
     }
-  }
 
-  groupMap[groupName].push(childCol)
-})
+    groupMap[groupName].push(childCol)
+  })
 
   // ----------------------------------------------------------------
   // 3-2) Create actual parent columns (groups) from the groupMap
   // ----------------------------------------------------------------
-  const nestedColumns: ColumnDef<ContractDetail>[] = [];
+  const nestedColumns: ColumnDef<ContractDetailParsed>[] = [];
 
   // Order can be fixed by pre-defining group order or sorting
   // Here we just use Object.entries order
-- 
cgit v1.2.3

