diff options
Diffstat (limited to 'lib/tbe-last/table/tbe-last-table-columns.tsx')
| -rw-r--r-- | lib/tbe-last/table/tbe-last-table-columns.tsx | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/lib/tbe-last/table/tbe-last-table-columns.tsx b/lib/tbe-last/table/tbe-last-table-columns.tsx new file mode 100644 index 00000000..71b3acde --- /dev/null +++ b/lib/tbe-last/table/tbe-last-table-columns.tsx @@ -0,0 +1,376 @@ +// lib/tbe-last/table/tbe-last-table-columns.tsx + +"use client" + +import * as React from "react" +import { type ColumnDef } from "@tanstack/react-table" +import { FileText, MessageSquare, Package, ListChecks } from "lucide-react" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Checkbox } from "@/components/ui/checkbox" +import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" +import { formatDate } from "@/lib/utils" +import { TbeLastView } from "@/db/schema/tbeLastView" + +interface GetColumnsProps { + onOpenSessionDetail: (sessionId: number) => void; + onOpenDocuments: (sessionId: number) => void; + onOpenPrItems: (rfqId: number) => void; + onOpenEvaluation: (session: TbeLastView) => void; +} + +export function getColumns({ + onOpenSessionDetail, + onOpenDocuments, + onOpenPrItems, + onOpenEvaluation, +}: GetColumnsProps): ColumnDef<TbeLastView>[] { + + const columns: ColumnDef<TbeLastView>[] = [ + // Select Column + { + 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" + /> + ), + size: 40, + enableSorting: false, + enableHiding: false, + }, + + // TBE Session Code + { + accessorKey: "sessionCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="TBE Code" /> + ), + cell: ({ row }) => { + const sessionId = row.original.tbeSessionId; + const sessionCode = row.original.sessionCode; + + return ( + <Button + variant="link" + className="p-0 h-auto font-medium" + onClick={() => onOpenSessionDetail(sessionId)} + > + {sessionCode} + </Button> + ); + }, + size: 120, + }, + + // RFQ Info Group + { + id: "rfqInfo", + header: "RFQ Information", + columns: [ + { + accessorKey: "rfqCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="RFQ Code" /> + ), + cell: ({ row }) => row.original.rfqCode, + size: 120, + }, + { + accessorKey: "rfqTitle", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="RFQ Title" /> + ), + cell: ({ row }) => row.original.rfqTitle || "-", + size: 200, + }, + { + accessorKey: "rfqDueDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Due Date" /> + ), + cell: ({ row }) => { + const date = row.original.rfqDueDate; + return date ? formatDate(date, "KR") : "-"; + }, + size: 100, + }, + ], + }, + + // Package Info + { + id: "packageInfo", + header: "Package", + columns: [ + { + accessorKey: "packageNo", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Package No" /> + ), + cell: ({ row }) => { + const packageNo = row.original.packageNo; + const packageName = row.original.packageName; + + if (!packageNo) return "-"; + + return ( + <div className="flex flex-col"> + <span className="font-medium">{packageNo}</span> + {packageName && ( + <span className="text-xs text-muted-foreground">{packageName}</span> + )} + </div> + ); + }, + size: 150, + }, + ], + }, + + // Project Info + { + accessorKey: "projectCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Project" /> + ), + cell: ({ row }) => { + const projectCode = row.original.projectCode; + const projectName = row.original.projectName; + + if (!projectCode) return "-"; + + return ( + <div className="flex flex-col"> + <span className="font-medium">{projectCode}</span> + {projectName && ( + <span className="text-xs text-muted-foreground">{projectName}</span> + )} + </div> + ); + }, + size: 150, + }, + + // Vendor Info + { + id: "vendorInfo", + header: "Vendor", + columns: [ + { + accessorKey: "vendorCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Vendor Code" /> + ), + cell: ({ row }) => row.original.vendorCode || "-", + size: 100, + }, + { + accessorKey: "vendorName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Vendor Name" /> + ), + cell: ({ row }) => row.original.vendorName, + size: 200, + }, + ], + }, + + // TBE Status + { + accessorKey: "sessionStatus", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Status" /> + ), + cell: ({ row }) => { + const status = row.original.sessionStatus; + + let variant: "default" | "secondary" | "outline" | "destructive" = "outline"; + + switch (status) { + case "준비중": + variant = "outline"; + break; + case "진행중": + variant = "default"; + break; + case "검토중": + variant = "secondary"; + break; + case "완료": + variant = "default"; + break; + case "보류": + variant = "destructive"; + break; + } + + return <Badge variant={variant}>{status}</Badge>; + }, + size: 100, + }, + + // Evaluation Result + { + accessorKey: "evaluationResult", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Result" /> + ), + cell: ({ row }) => { + const result = row.original.evaluationResult; + const session = row.original; + + if (!result) { + return ( + <Button + variant="outline" + size="sm" + onClick={() => onOpenEvaluation(session)} + > + 평가입력 + </Button> + ); + } + + let variant: "default" | "secondary" | "destructive" = "default"; + let displayText = result; + + switch (result) { + case "pass": + variant = "default"; + displayText = "Pass"; + break; + case "conditional_pass": + variant = "secondary"; + displayText = "Conditional"; + break; + case "non_pass": + variant = "destructive"; + displayText = "Non-Pass"; + break; + } + + return ( + <Button + variant="link" + className="p-0 h-auto" + onClick={() => onOpenEvaluation(session)} + > + <Badge variant={variant}>{displayText}</Badge> + </Button> + ); + }, + size: 120, + }, + + // PR Items + { + id: "prItems", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="PR Items" /> + ), + cell: ({ row }) => { + const rfqId = row.original.rfqId; + const totalCount = row.original.prItemsCount; + const majorCount = row.original.majorItemsCount; + + return ( + <Button + variant="ghost" + size="sm" + className="h-8 px-2" + onClick={() => onOpenPrItems(rfqId)} + > + <ListChecks className="h-4 w-4 mr-1" /> + <span className="text-xs"> + {totalCount} ({majorCount}) + </span> + </Button> + ); + }, + size: 100, + enableSorting: false, + }, + + // Documents + { + id: "documents", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Documents" /> + ), + cell: ({ row }) => { + const sessionId = row.original.tbeSessionId; + const buyerDocs = row.original.buyerDocumentsCount; + const vendorDocs = row.original.vendorDocumentsCount; + const reviewedDocs = row.original.reviewedDocumentsCount; + const totalDocs = buyerDocs + vendorDocs; + + return ( + <Button + variant="ghost" + size="sm" + className="h-8 px-2" + onClick={() => onOpenDocuments(sessionId)} + > + <FileText className="h-4 w-4 mr-1" /> + <span className="text-xs"> + {reviewedDocs}/{totalDocs} + </span> + </Button> + ); + }, + size: 100, + enableSorting: false, + }, + + // Comments + { + id: "comments", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Comments" /> + ), + cell: ({ row }) => { + const sessionId = row.original.tbeSessionId; + const totalComments = row.original.totalCommentsCount; + const unresolvedComments = row.original.unresolvedCommentsCount; + + return ( + <Button + variant="ghost" + size="sm" + className="h-8 px-2 relative" + onClick={() => onOpenDocuments(sessionId)} + > + <MessageSquare className="h-4 w-4" /> + {totalComments > 0 && ( + <Badge + variant={unresolvedComments > 0 ? "destructive" : "secondary"} + className="absolute -top-1 -right-1 h-4 min-w-[1rem] p-0 text-[0.625rem]" + > + {unresolvedComments > 0 ? unresolvedComments : totalComments} + </Badge> + )} + </Button> + ); + }, + size: 80, + enableSorting: false, + }, + ]; + + return columns; +}
\ No newline at end of file |
