diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-03-28 00:42:08 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-03-28 00:42:08 +0000 |
| commit | b8e8328b1ffffb80bf4ebb776a4a24e5680fc5bc (patch) | |
| tree | bbb4d82cee5f3fbf107e0648dea9a8f66e2710c4 /lib/po/table/po-table-columns.tsx | |
| parent | 34bbeb86c1a8d24b5f526710889b5e54d699cfd0 (diff) | |
테이블 칼럼 리사이즈 및 핀 충돌 해결
Diffstat (limited to 'lib/po/table/po-table-columns.tsx')
| -rw-r--r-- | lib/po/table/po-table-columns.tsx | 300 |
1 files changed, 187 insertions, 113 deletions
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 |
