From c228a89c2834ee63b209bad608837c39643f350e Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Mon, 28 Jul 2025 11:44:16 +0000
Subject: (대표님) 의존성 docx 추가, basicContract API, gtc(계약일반조건),
 벤더평가 esg 평가데이터 내보내기 개선, S-EDP 피드백 대응(CLS_ID, ITEM NO 등)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../table/gtc-clauses-table-columns.tsx            | 283 +++++++++++++++++++++
 1 file changed, 283 insertions(+)
 create mode 100644 lib/gtc-contract/gtc-clauses/table/gtc-clauses-table-columns.tsx

(limited to 'lib/gtc-contract/gtc-clauses/table/gtc-clauses-table-columns.tsx')

diff --git a/lib/gtc-contract/gtc-clauses/table/gtc-clauses-table-columns.tsx b/lib/gtc-contract/gtc-clauses/table/gtc-clauses-table-columns.tsx
new file mode 100644
index 00000000..29efeb9c
--- /dev/null
+++ b/lib/gtc-contract/gtc-clauses/table/gtc-clauses-table-columns.tsx
@@ -0,0 +1,283 @@
+"use client"
+
+import * as React from "react"
+import { type DataTableRowAction } from "@/types/table"
+import { type ColumnDef } from "@tanstack/react-table"
+import { Ellipsis, Edit, Trash2, Plus, Copy } from "lucide-react"
+import { cn, compareItemNumber, formatDate } from "@/lib/utils"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Checkbox } from "@/components/ui/checkbox"
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuSeparator,
+  DropdownMenuShortcut,
+  DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import {
+  Tooltip,
+  TooltipContent,
+  TooltipProvider,
+  TooltipTrigger,
+} from "@/components/ui/tooltip"
+import { toast } from "sonner"
+import { useSession } from "next-auth/react"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import { type GtcClauseTreeView } from "@/db/schema/gtc"
+
+interface GetColumnsProps {
+  setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<GtcClauseTreeView> | null>>
+  documentId: number
+}
+
+export function getColumns({ setRowAction, documentId }: GetColumnsProps): ColumnDef<GtcClauseTreeView>[] {
+  // 1) select
+  const selectColumn: ColumnDef<GtcClauseTreeView> = {
+    id: "select",
+    header: ({ table }) => (
+      <Checkbox
+        checked={
+          table.getIsAllPageRowsSelected() ||
+          (table.getIsSomePageRowsSelected() && "indeterminate")
+        }
+        onCheckedChange={(v) => table.toggleAllPageRowsSelected(!!v)}
+        aria-label="Select all"
+        className="translate-y-0.5"
+      />
+    ),
+    cell: ({ row }) => (
+      <Checkbox
+        checked={row.getIsSelected()}
+        onCheckedChange={(v) => row.toggleSelected(!!v)}
+        aria-label="Select row"
+        className="translate-y-0.5"
+      />
+    ),
+    size: 40,
+    enableSorting: false,
+    enableHiding: false,
+  }
+
+  // 2) 조항 정보
+  const clauseInfoColumns: ColumnDef<GtcClauseTreeView>[] = [
+    {
+      accessorKey: "itemNumber",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="채번" />,
+      cell: ({ row }) => {
+        const itemNumber = row.getValue("itemNumber") as string
+        const depth = row.original.depth
+        const childrenCount = row.original.childrenCount
+        return (
+          <div className="flex items-center gap-2">
+            <div style={{ marginLeft: `${depth * 20}px` }} className="flex items-center gap-1">
+              <span className="font-mono text-sm font-medium">{itemNumber}</span>
+              {childrenCount > 0 && (
+                <TooltipProvider>
+                  <Tooltip>
+                    <TooltipTrigger>
+                      <Badge variant="outline" className="h-5 px-1 text-xs">
+                        {childrenCount}
+                      </Badge>
+                    </TooltipTrigger>
+                    <TooltipContent>
+                      <p>{childrenCount}개의 하위 조항</p>
+                    </TooltipContent>
+                  </Tooltip>
+                </TooltipProvider>
+              )}
+            </div>
+          </div>
+        )
+      },
+      size: 100,
+      enableResizing: true,
+      sortingFn: (rowA, rowB, colId) =>
+        compareItemNumber(rowA.getValue<string>(colId), rowB.getValue<string>(colId)),
+      meta: { excelHeader: "채번" },
+    },
+    {
+      accessorKey: "category",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="분류" />,
+      cell: ({ row }) => {
+        const category = row.getValue("category") as string
+        return category ? (
+          <Badge variant="secondary" className="text-xs">{category}</Badge>
+        ) : (
+          <span className="text-muted-foreground">-</span>
+        )
+      },
+      size: 100,
+      enableResizing: true,
+      meta: { excelHeader: "분류" },
+    },
+    {
+      accessorKey: "subtitle",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="소제목" />,
+      cell: ({ row }) => {
+        const subtitle = row.getValue("subtitle") as string
+        const depth = row.original.depth
+        return (
+          <div className="flex flex-col min-w-0">
+            <span
+              className={cn(
+                "font-medium truncate",
+                depth === 0 && "text-base",
+                depth === 1 && "text-sm",
+                depth >= 2 && "text-sm text-muted-foreground",
+              )}
+              title={subtitle}
+            >
+              {subtitle}
+            </span>
+          </div>
+        )
+      },
+      size: 150,
+      enableResizing: true,
+      meta: { excelHeader: "소제목" },
+    },
+    {
+      accessorKey: "content",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="상세항목" />,
+      cell: ({ row }) => {
+        const content = row.getValue("content") as string | null
+        if (!content) {
+          return (
+            <div className="flex items-center gap-2">
+              <Badge variant="outline" className="text-xs">그룹핑 조항</Badge>
+              <span className="text-xs text-muted-foreground">상세내용 없음</span>
+            </div>
+          )
+        }
+        const truncated = content.length > 100 ? `${content.substring(0, 100)}...` : content
+        return (
+          <TooltipProvider>
+            <Tooltip>
+              <TooltipTrigger asChild>
+                <div className="">
+                  <p className="text-sm line-clamp-2 text-muted-foreground">{content}</p>
+                </div>
+              </TooltipTrigger>
+              <TooltipContent className="max-w-sm">
+                <p className="whitespace-pre-wrap">{content}</p>
+              </TooltipContent>
+            </Tooltip>
+          </TooltipProvider>
+        )
+      },
+      size: 200,
+      maxSize: 500,
+      enableResizing: true,
+      meta: { excelHeader: "상세항목" },
+    },
+  ]
+
+  // 3) 등록/수정 정보
+  const auditColumns: ColumnDef<GtcClauseTreeView>[] = [
+    {
+      accessorKey: "createdAt",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="작성일" />,
+      cell: ({ row }) => {
+        const date = row.getValue("createdAt") as Date
+        return date ? formatDate(date, "KR") : "-"
+      },
+      size: 120,
+      enableResizing: true,
+      meta: { excelHeader: "작성일" },
+    },
+    {
+      accessorKey: "createdByName",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="작성자" />,
+      cell: ({ row }) => {
+        const v = row.getValue("createdByName") as string
+        return v ? <span className="text-sm">{v}</span> : <span className="text-muted-foreground">-</span>
+      },
+      size: 80,
+      enableResizing: true,
+      meta: { excelHeader: "작성자" },
+    },
+    {
+      accessorKey: "updatedAt",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="수정일" />,
+      cell: ({ row }) => {
+        const date = row.getValue("updatedAt") as Date
+        return <span className="text-sm">{date ? formatDate(date, "KR") : "-"}</span>
+      },
+      size: 120,
+      enableResizing: true,
+      meta: { excelHeader: "수정일" },
+    },
+    {
+      accessorKey: "updatedByName",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="수정자" />,
+      cell: ({ row }) => {
+        const v = row.getValue("updatedByName") as string
+        return v ? <span className="text-sm">{v}</span> : <span className="text-muted-foreground">-</span>
+      },
+      size: 80,
+      enableResizing: true,
+      meta: { excelHeader: "수정자" },
+    },
+  ]
+
+  // 4) actions
+  const actionsColumn: ColumnDef<GtcClauseTreeView> = {
+    id: "actions",
+    enableHiding: false,
+    cell: function Cell({ row }) {
+      const { data: session } = useSession()
+      const gtcClause = row.original
+      const currentUserId = React.useMemo(
+        () => (session?.user?.id ? Number(session.user.id) : null),
+        [session],
+      )
+
+      const handleEdit = () => setRowAction({ row, type: "update" })
+      const handleDelete = () => setRowAction({ row, type: "delete" })
+      const handleAddSubClause = () => setRowAction({ row, type: "addSubClause" })
+      const handleDuplicate = () => setRowAction({ row, type: "duplicate" })
+
+      return (
+        <DropdownMenu>
+          <DropdownMenuTrigger asChild>
+            <Button aria-label="Open menu" variant="ghost" className="flex size-8 p-0 data-[state=open]:bg-muted">
+              <Ellipsis className="size-4" aria-hidden="true" />
+            </Button>
+          </DropdownMenuTrigger>
+          <DropdownMenuContent align="end" className="w-48">
+            <DropdownMenuItem onSelect={handleEdit}>
+              <Edit className="mr-2 h-4 w-4" />
+              편집
+            </DropdownMenuItem>
+            <DropdownMenuItem onSelect={handleAddSubClause}>
+              <Plus className="mr-2 h-4 w-4" />
+              하위 조항 추가
+            </DropdownMenuItem>
+            <DropdownMenuItem onSelect={handleDuplicate}>
+              <Copy className="mr-2 h-4 w-4" />
+              복제
+            </DropdownMenuItem>
+            <DropdownMenuSeparator />
+            <DropdownMenuItem onSelect={handleDelete} className="text-destructive">
+              <Trash2 className="mr-2 h-4 w-4" />
+              삭제
+              <DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
+            </DropdownMenuItem>
+          </DropdownMenuContent>
+        </DropdownMenu>
+      )
+    },
+    size: 40,
+    maxSize: 40,
+  }
+
+  // 🔹 그룹 헤더 제거: 평탄화된 컬럼 배열 반환
+  return [
+    selectColumn,
+    ...clauseInfoColumns,
+    ...auditColumns,
+    actionsColumn,
+  ]
+}
\ No newline at end of file
-- 
cgit v1.2.3

