summaryrefslogtreecommitdiff
path: root/lib/gtc-contract/gtc-clauses/table/gtc-clauses-table-columns.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gtc-contract/gtc-clauses/table/gtc-clauses-table-columns.tsx')
-rw-r--r--lib/gtc-contract/gtc-clauses/table/gtc-clauses-table-columns.tsx283
1 files changed, 283 insertions, 0 deletions
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