diff options
Diffstat (limited to 'lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx')
| -rw-r--r-- | lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx new file mode 100644 index 00000000..5c6971cc --- /dev/null +++ b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx @@ -0,0 +1,365 @@ +"use client" + +import * as React from "react" +import { type ColumnDef } from "@tanstack/react-table" +import { Edit } from "lucide-react" +import { formatCurrency, formatDate, formatDateTime } from "@/lib/utils" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Checkbox } from "@/components/ui/checkbox" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" +import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header" +import { + TechSalesVendorQuotations, + TECH_SALES_QUOTATION_STATUS_CONFIG +} from "@/db/schema" +import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime" + +interface QuotationWithRfqCode extends TechSalesVendorQuotations { + rfqCode?: string; + materialCode?: string; + dueDate?: Date; + rfqStatus?: string; + itemName?: string; + projNm?: string; + quotationCode?: string | null; + quotationVersion?: number | null; + rejectionReason?: string | null; + acceptedAt?: Date | null; +} + +interface GetColumnsProps { + router: AppRouterInstance; +} + +export function getColumns({ router }: GetColumnsProps): ColumnDef<QuotationWithRfqCode>[] { + return [ + { + id: "select", + header: ({ table }) => ( + <Checkbox + checked={ + table.getIsAllPageRowsSelected() || + (table.getIsSomePageRowsSelected() && "indeterminate") + } + onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} + aria-label="모두 선택" + className="translate-y-0.5" + /> + ), + cell: ({ row }) => ( + <Checkbox + checked={row.getIsSelected()} + onCheckedChange={(value) => row.toggleSelected(!!value)} + aria-label="행 선택" + className="translate-y-0.5" + /> + ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "id", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="ID" /> + ), + cell: ({ row }) => ( + <div className="w-20"> + <span className="font-mono text-xs">{row.getValue("id")}</span> + </div> + ), + enableSorting: true, + enableHiding: true, + }, + { + accessorKey: "rfqCode", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="RFQ 번호" /> + ), + cell: ({ row }) => { + const rfqCode = row.getValue("rfqCode") as string; + return ( + <div className="min-w-32"> + <span className="font-mono text-sm">{rfqCode || "N/A"}</span> + </div> + ); + }, + enableSorting: true, + enableHiding: false, + }, + { + accessorKey: "materialCode", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="자재 코드" /> + ), + cell: ({ row }) => { + const materialCode = row.getValue("materialCode") as string; + return ( + <div className="min-w-32"> + <span className="font-mono text-sm">{materialCode || "N/A"}</span> + </div> + ); + }, + enableSorting: true, + enableHiding: true, + }, + { + accessorKey: "itemName", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="품목명" /> + ), + cell: ({ row }) => { + const itemName = row.getValue("itemName") as string; + return ( + <div className="min-w-48 max-w-64"> + <TooltipProvider> + <Tooltip> + <TooltipTrigger asChild> + <span className="truncate block text-sm"> + {itemName || "N/A"} + </span> + </TooltipTrigger> + <TooltipContent> + <p className="max-w-xs">{itemName || "N/A"}</p> + </TooltipContent> + </Tooltip> + </TooltipProvider> + </div> + ); + }, + enableSorting: false, + enableHiding: true, + }, + { + accessorKey: "projNm", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="프로젝트명" /> + ), + cell: ({ row }) => { + const projNm = row.getValue("projNm") as string; + return ( + <div className="min-w-48 max-w-64"> + <TooltipProvider> + <Tooltip> + <TooltipTrigger asChild> + <span className="truncate block text-sm"> + {projNm || "N/A"} + </span> + </TooltipTrigger> + <TooltipContent> + <p className="max-w-xs">{projNm || "N/A"}</p> + </TooltipContent> + </Tooltip> + </TooltipProvider> + </div> + ); + }, + enableSorting: false, + enableHiding: true, + }, + { + accessorKey: "status", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="상태" /> + ), + cell: ({ row }) => { + const status = row.getValue("status") as string; + + const statusConfig = TECH_SALES_QUOTATION_STATUS_CONFIG[status as keyof typeof TECH_SALES_QUOTATION_STATUS_CONFIG] || { + label: status, + variant: "secondary" as const + }; + + return ( + <div className="w-24"> + <Badge variant={statusConfig.variant} className="text-xs"> + {statusConfig.label} + </Badge> + </div> + ); + }, + enableSorting: true, + enableHiding: false, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, + }, + { + accessorKey: "currency", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="통화" /> + ), + cell: ({ row }) => { + const currency = row.getValue("currency") as string; + return ( + <div className="w-16"> + <span className="font-mono text-sm">{currency || "N/A"}</span> + </div> + ); + }, + enableSorting: true, + enableHiding: true, + }, + { + accessorKey: "totalPrice", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="총액" /> + ), + cell: ({ row }) => { + const totalPrice = row.getValue("totalPrice") as string; + const currency = row.getValue("currency") as string; + + if (!totalPrice || totalPrice === "0") { + return ( + <div className="w-32 text-right"> + <span className="text-muted-foreground text-sm">미입력</span> + </div> + ); + } + + return ( + <div className="w-32 text-right"> + <span className="font-mono text-sm"> + {formatCurrency(parseFloat(totalPrice), currency || "USD")} + </span> + </div> + ); + }, + enableSorting: true, + enableHiding: true, + }, + { + accessorKey: "validUntil", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="유효기간" /> + ), + cell: ({ row }) => { + const validUntil = row.getValue("validUntil") as Date; + return ( + <div className="w-28"> + <span className="text-sm"> + {validUntil ? formatDate(validUntil) : "N/A"} + </span> + </div> + ); + }, + enableSorting: true, + enableHiding: true, + }, + { + accessorKey: "submittedAt", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="제출일" /> + ), + cell: ({ row }) => { + const submittedAt = row.getValue("submittedAt") as Date; + return ( + <div className="w-36"> + <span className="text-sm"> + {submittedAt ? formatDateTime(submittedAt) : "미제출"} + </span> + </div> + ); + }, + enableSorting: true, + enableHiding: true, + }, + { + accessorKey: "dueDate", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="마감일" /> + ), + cell: ({ row }) => { + const dueDate = row.getValue("dueDate") as Date; + const isOverdue = dueDate && new Date() > new Date(dueDate); + + return ( + <div className="w-28"> + <span className={`text-sm ${isOverdue ? "text-red-600 font-medium" : ""}`}> + {dueDate ? formatDate(dueDate) : "N/A"} + </span> + </div> + ); + }, + enableSorting: true, + enableHiding: true, + }, + { + accessorKey: "createdAt", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="생성일" /> + ), + cell: ({ row }) => { + const createdAt = row.getValue("createdAt") as Date; + return ( + <div className="w-36"> + <span className="text-sm"> + {createdAt ? formatDateTime(createdAt) : "N/A"} + </span> + </div> + ); + }, + enableSorting: true, + enableHiding: true, + }, + { + accessorKey: "updatedAt", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="수정일" /> + ), + cell: ({ row }) => { + const updatedAt = row.getValue("updatedAt") as Date; + return ( + <div className="w-36"> + <span className="text-sm"> + {updatedAt ? formatDateTime(updatedAt) : "N/A"} + </span> + </div> + ); + }, + enableSorting: true, + enableHiding: true, + }, + { + id: "actions", + header: "작업", + cell: ({ row }) => { + const quotation = row.original; + const rfqCode = quotation.rfqCode || "N/A"; + const tooltipText = `${rfqCode} 견적서 작성`; + + return ( + <div className="w-16"> + <TooltipProvider> + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="ghost" + size="icon" + onClick={() => { + router.push(`/ko/partners/techsales/rfq-ship/${quotation.id}`); + }} + className="h-8 w-8" + > + <Edit className="h-4 w-4" /> + <span className="sr-only">견적서 작성</span> + </Button> + </TooltipTrigger> + <TooltipContent> + <p>{tooltipText}</p> + </TooltipContent> + </Tooltip> + </TooltipProvider> + </div> + ); + }, + enableSorting: false, + enableHiding: false, + }, + ]; +}
\ No newline at end of file |
