diff options
Diffstat (limited to 'lib/b-rfq/initial/initial-rfq-detail-columns.tsx')
| -rw-r--r-- | lib/b-rfq/initial/initial-rfq-detail-columns.tsx | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/lib/b-rfq/initial/initial-rfq-detail-columns.tsx b/lib/b-rfq/initial/initial-rfq-detail-columns.tsx new file mode 100644 index 00000000..f7ac0960 --- /dev/null +++ b/lib/b-rfq/initial/initial-rfq-detail-columns.tsx @@ -0,0 +1,386 @@ +// initial-rfq-detail-columns.tsx +"use client" + +import * as React from "react" +import { type ColumnDef } from "@tanstack/react-table" +import { + Ellipsis, Building, Calendar, Eye, + MessageSquare, Settings, CheckCircle2, XCircle +} from "lucide-react" + +import { 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, DropdownMenuTrigger +} from "@/components/ui/dropdown-menu" +import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" + +interface GetInitialRfqDetailColumnsProps { + onSelectDetail?: (detail: any) => void +} + +export function getInitialRfqDetailColumns({ + onSelectDetail +}: GetInitialRfqDetailColumnsProps = {}): ColumnDef<any>[] { + + return [ + /** ───────────── 체크박스 ───────────── */ + { + 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, + }, + + /** ───────────── RFQ 정보 ───────────── */ + { + accessorKey: "rfqCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="RFQ 코드" /> + ), + cell: ({ row }) => ( + <Button + variant="link" + className="p-0 h-auto font-medium text-blue-600 hover:text-blue-800" + onClick={() => onSelectDetail?.(row.original)} + > + {row.getValue("rfqCode") as string} + </Button> + ), + size: 120, + }, + { + accessorKey: "rfqStatus", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="RFQ 상태" /> + ), + cell: ({ row }) => { + const status = row.getValue("rfqStatus") as string + const getStatusColor = (status: string) => { + switch (status) { + case "DRAFT": return "secondary" + case "Doc. Received": return "outline" + case "PIC Assigned": return "default" + case "Doc. Confirmed": return "default" + case "Init. RFQ Sent": return "default" + case "Init. RFQ Answered": return "success" + case "TBE started": return "warning" + case "TBE finished": return "warning" + case "Final RFQ Sent": return "default" + case "Quotation Received": return "success" + case "Vendor Selected": return "success" + default: return "secondary" + } + } + return ( + <Badge variant={getStatusColor(status) as any}> + {status} + </Badge> + ) + }, + size: 140 + }, + { + accessorKey: "initialRfqStatus", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="초기 RFQ 상태" /> + ), + cell: ({ row }) => { + const status = row.getValue("initialRfqStatus") as string + const getInitialStatusColor = (status: string) => { + switch (status) { + case "PENDING": return "outline" + case "SENT": return "default" + case "RESPONDED": return "success" + case "EXPIRED": return "destructive" + case "CANCELLED": return "secondary" + default: return "secondary" + } + } + return ( + <Badge variant={getInitialStatusColor(status) as any}> + {status} + </Badge> + ) + }, + size: 120 + }, + + /** ───────────── 벤더 정보 ───────────── */ + { + id: "vendorInfo", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="벤더 정보" /> + ), + cell: ({ row }) => { + const vendorName = row.original.vendorName as string + const vendorCode = row.original.vendorCode as string + const vendorCountry = row.original.vendorCountry as string + const businessSize = row.original.vendorBusinessSize as string + + return ( + <div className="space-y-1"> + <div className="flex items-center gap-2"> + <Building className="h-4 w-4 text-muted-foreground" /> + <div className="font-medium">{vendorName}</div> + </div> + <div className="text-sm text-muted-foreground"> + {vendorCode} • {vendorCountry} + </div> + {businessSize && ( + <Badge variant="outline" className="text-xs"> + {businessSize} + </Badge> + )} + </div> + ) + }, + size: 200, + }, + + /** ───────────── 날짜 정보 ───────────── */ + { + accessorKey: "dueDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="마감일" /> + ), + cell: ({ row }) => { + const dueDate = row.getValue("dueDate") as Date + const isOverdue = dueDate && new Date(dueDate) < new Date() + + return dueDate ? ( + <div className={`flex items-center gap-2 ${isOverdue ? 'text-red-600' : ''}`}> + <Calendar className="h-4 w-4" /> + <div> + <div className="font-medium">{formatDate(dueDate)}</div> + {isOverdue && ( + <div className="text-xs text-red-600">지연</div> + )} + </div> + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 120, + }, + { + accessorKey: "validDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="유효일" /> + ), + cell: ({ row }) => { + const validDate = row.getValue("validDate") as Date + return validDate ? ( + <div className="text-sm"> + {formatDate(validDate)} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 100, + }, + + /** ───────────── Incoterms ───────────── */ + { + id: "incoterms", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Incoterms" /> + ), + cell: ({ row }) => { + const code = row.original.incotermsCode as string + const description = row.original.incotermsDescription as string + + return code ? ( + <div className="space-y-1"> + <Badge variant="outline">{code}</Badge> + {description && ( + <div className="text-xs text-muted-foreground max-w-[150px] truncate" title={description}> + {description} + </div> + )} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 120, + }, + + /** ───────────── 플래그 정보 ───────────── */ + { + id: "flags", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="플래그" /> + ), + cell: ({ row }) => { + const shortList = row.original.shortList as boolean + const returnYn = row.original.returnYn as boolean + const cpRequestYn = row.original.cpRequestYn as boolean + const prjectGtcYn = row.original.prjectGtcYn as boolean + + return ( + <div className="flex flex-wrap gap-1"> + {shortList && ( + <Badge variant="secondary" className="text-xs"> + <CheckCircle2 className="h-3 w-3 mr-1" /> + Short List + </Badge> + )} + {returnYn && ( + <Badge variant="outline" className="text-xs"> + Return + </Badge> + )} + {cpRequestYn && ( + <Badge variant="outline" className="text-xs"> + CP Request + </Badge> + )} + {prjectGtcYn && ( + <Badge variant="outline" className="text-xs"> + GTC + </Badge> + )} + </div> + ) + }, + size: 150, + }, + + /** ───────────── 분류 정보 ───────────── */ + { + id: "classification", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="분류" /> + ), + cell: ({ row }) => { + const classification = row.original.classification as string + const sparepart = row.original.sparepart as string + + return ( + <div className="space-y-1"> + {classification && ( + <div className="text-sm font-medium max-w-[120px] truncate" title={classification}> + {classification} + </div> + )} + {sparepart && ( + <Badge variant="outline" className="text-xs"> + {sparepart} + </Badge> + )} + </div> + ) + }, + size: 120, + }, + + /** ───────────── 리비전 정보 ───────────── */ + { + accessorKey: "returnRevision", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="리비전" /> + ), + cell: ({ row }) => { + const revision = row.getValue("returnRevision") as number + return revision ? ( + <Badge variant="outline"> + Rev. {revision} + </Badge> + ) : ( + <span className="text-muted-foreground">-</span> + ) + }, + size: 80, + }, + + /** ───────────── 등록/수정 정보 ───────────── */ + { + accessorKey: "createdAt", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="등록일" /> + ), + cell: ({ row }) => { + const created = row.getValue("createdAt") as Date + const updated = row.original.updatedAt as Date + + return ( + <div className="space-y-1"> + <div className="text-sm">{formatDate(created)}</div> + {updated && new Date(updated) > new Date(created) && ( + <div className="text-xs text-blue-600"> + 수정: {formatDate(updated)} + </div> + )} + </div> + ) + }, + size: 120, + }, + + /** ───────────── 액션 ───────────── */ + { + id: "actions", + enableHiding: false, + cell: ({ row }) => { + 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 onClick={() => onSelectDetail?.(row.original)}> + <Eye className="mr-2 h-4 w-4" /> + 상세 보기 + </DropdownMenuItem> + <DropdownMenuItem> + <MessageSquare className="mr-2 h-4 w-4" /> + 벤더 응답 보기 + </DropdownMenuItem> + <DropdownMenuSeparator /> + <DropdownMenuItem> + <Settings className="mr-2 h-4 w-4" /> + 설정 수정 + </DropdownMenuItem> + <DropdownMenuItem className="text-red-600"> + <XCircle className="mr-2 h-4 w-4" /> + 삭제 + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + ) + }, + size: 40, + }, + ] +}
\ No newline at end of file |
