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 | 1380 |
1 files changed, 709 insertions, 671 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 index 39de94ed..328def80 100644 --- a/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx +++ b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx @@ -1,672 +1,710 @@ -"use client" - -import * as React from "react" -import { type ColumnDef } from "@tanstack/react-table" -import { Edit, Paperclip, Package } 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 { - TechSalesVendorQuotations, - TECH_SALES_QUOTATION_STATUS_CONFIG, - TECH_SALES_QUOTATION_STATUSES -} from "@/db/schema" -import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime" -import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" - -interface QuotationWithRfqCode extends TechSalesVendorQuotations { - // RFQ 관련 정보 - rfqCode?: string; - materialCode?: string; - dueDate?: Date; - rfqStatus?: string; - - // 아이템 정보 - itemName?: string; - itemCount?: number; - - // 프로젝트 정보 - projNm?: string; - pspid?: string; - sector?: string; - - // RFQ 정보 - description?: string; - - // 벤더 정보 - vendorName?: string; - vendorCode?: string; - - // 사용자 정보 - createdByName?: string | null; - updatedByName?: string | null; - - // 첨부파일 개수 - attachmentCount?: number; -} - -interface GetColumnsProps { - router: AppRouterInstance; - openAttachmentsSheet: (rfqId: number) => void; - openItemsDialog: (rfq: { id: number; rfqCode?: string; status?: string; rfqType?: "SHIP" | "TOP" | "HULL"; }) => void; -} - -export function getColumns({ router, openAttachmentsSheet, openItemsDialog }: 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 }) => { - const isRejected = row.original.status === TECH_SALES_QUOTATION_STATUSES.REJECTED; - const isAccepted = row.original.status === TECH_SALES_QUOTATION_STATUSES.ACCEPTED; - const isDisabled = isRejected || isAccepted; - - return ( - <Checkbox - checked={row.getIsSelected()} - onCheckedChange={(value) => row.toggleSelected(!!value)} - aria-label="행 선택" - className="translate-y-0.5" - disabled={isDisabled} - /> - ); - }, - enableSorting: false, - enableHiding: false, - }, - // { - // accessorKey: "id", - // header: ({ column }) => ( - // <DataTableColumnHeaderSimple 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 }) => ( - <DataTableColumnHeaderSimple 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: "vendorName", - // header: ({ column }) => ( - // <DataTableColumnHeaderSimple column={column} title="벤더명" /> - // ), - // cell: ({ row }) => { - // const vendorName = row.getValue("vendorName") as string; - // return ( - // <div className="min-w-32"> - // <span className="text-sm">{vendorName || "N/A"}</span> - // </div> - // ); - // }, - // enableSorting: true, - // enableHiding: false, - // }, - // { - // accessorKey: "vendorCode", - // header: ({ column }) => ( - // <DataTableColumnHeaderSimple column={column} title="벤더 코드" /> - // ), - // cell: ({ row }) => { - // const vendorCode = row.getValue("vendorCode") as string; - // return ( - // <div className="min-w-24"> - // <span className="font-mono text-sm">{vendorCode || "N/A"}</span> - // </div> - // ); - // }, - // enableSorting: true, - // enableHiding: true, - // }, - // { - // accessorKey: "materialCode", - // header: ({ column }) => ( - // <DataTableColumnHeaderSimple 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 }) => ( - // <DataTableColumnHeaderSimple 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: true, - // enableHiding: true, - // }, - { - accessorKey: "description", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="RFQ title" /> - ), - cell: ({ row }) => { - const description = row.getValue("description") as string; - return ( - <div className="min-w-48 max-w-64"> - <TooltipProvider> - <Tooltip> - <TooltipTrigger asChild> - <span className="truncate block text-sm"> - {description || "N/A"} - </span> - </TooltipTrigger> - <TooltipContent> - <p className="max-w-xs">{description || "N/A"}</p> - </TooltipContent> - </Tooltip> - </TooltipProvider> - </div> - ); - }, - enableSorting: true, - enableHiding: true, - }, - { - accessorKey: "projNm", - header: ({ column }) => ( - <DataTableColumnHeaderSimple 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: true, - enableHiding: true, - }, - // { - // accessorKey: "quotationCode", - // header: ({ column }) => ( - // <DataTableColumnHeaderSimple column={column} title="견적서 번호" /> - // ), - // cell: ({ row }) => { - // const quotationCode = row.getValue("quotationCode") as string; - // return ( - // <div className="min-w-32"> - // <span className="font-mono text-sm">{quotationCode || "미부여"}</span> - // </div> - // ); - // }, - // enableSorting: true, - // enableHiding: true, - // }, - // { - // accessorKey: "quotationVersion", - // header: ({ column }) => ( - // <DataTableColumnHeaderSimple column={column} title="버전" /> - // ), - // cell: ({ row }) => { - // const quotationVersion = row.getValue("quotationVersion") as number; - // return ( - // <div className="w-16 text-center"> - // <span className="text-sm">{quotationVersion || 1}</span> - // </div> - // ); - // }, - // enableSorting: true, - // enableHiding: true, - // }, - { - id: "items", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="아이템" /> - ), - cell: ({ row }) => { - const quotation = row.original - const itemCount = quotation.itemCount || 0 - - const handleClick = () => { - const rfq = { - id: quotation.rfqId, - rfqCode: quotation.rfqCode, - status: quotation.rfqStatus, - rfqType: "SHIP" as const, // 기본값 - } - openItemsDialog(rfq) - } - - return ( - <div className="w-20"> - <TooltipProvider> - <Tooltip> - <TooltipTrigger asChild> - <Button - variant="ghost" - size="sm" - className="relative h-8 w-8 p-0 group" - onClick={handleClick} - aria-label={`View ${itemCount} items`} - > - <Package className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" /> - {itemCount > 0 && ( - <span className="pointer-events-none absolute -top-1 -right-1 inline-flex h-4 min-w-[1rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-medium leading-none text-primary-foreground"> - {itemCount} - </span> - )} - <span className="sr-only"> - {itemCount > 0 ? `${itemCount} 아이템` : "아이템 없음"} - </span> - </Button> - </TooltipTrigger> - <TooltipContent> - <p>{itemCount > 0 ? `${itemCount}개 아이템 보기` : "아이템 없음"}</p> - </TooltipContent> - </Tooltip> - </TooltipProvider> - </div> - ) - }, - enableSorting: false, - enableHiding: true, - }, - { - id: "attachments", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="첨부파일" /> - ), - cell: ({ row }) => { - const quotation = row.original - const attachmentCount = quotation.attachmentCount || 0 - const handleClick = () => { - openAttachmentsSheet(quotation.rfqId) - } - - return ( - <div className="w-20"> - <TooltipProvider> - <Tooltip> - <TooltipTrigger asChild> - <Button - variant="ghost" - size="sm" - className="relative h-8 w-8 p-0 group" - onClick={handleClick} - aria-label={ - attachmentCount > 0 ? `View ${attachmentCount} attachments` : "No attachments" - } - > - <Paperclip className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" /> - {attachmentCount > 0 && ( - <span className="pointer-events-none absolute -top-1 -right-1 inline-flex h-4 min-w-[1rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-medium leading-none text-primary-foreground"> - {attachmentCount} - </span> - )} - <span className="sr-only"> - {attachmentCount > 0 ? `${attachmentCount} 첨부파일` : "첨부파일 없음"} - </span> - </Button> - </TooltipTrigger> - <TooltipContent> - <p>{attachmentCount > 0 ? `${attachmentCount}개 첨부파일 보기` : "첨부파일 없음"}</p> - </TooltipContent> - </Tooltip> - </TooltipProvider> - </div> - ) - }, - enableSorting: false, - enableHiding: true, - }, - { - accessorKey: "status", - header: ({ column }) => ( - <DataTableColumnHeaderSimple 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 }) => ( - <DataTableColumnHeaderSimple 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 }) => ( - <DataTableColumnHeaderSimple 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 }) => ( - <DataTableColumnHeaderSimple 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 }) => ( - <DataTableColumnHeaderSimple 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: "acceptedAt", - // header: ({ column }) => ( - // <DataTableColumnHeaderSimple column={column} title="승인일" /> - // ), - // cell: ({ row }) => { - // const acceptedAt = row.getValue("acceptedAt") as Date; - // return ( - // <div className="w-36"> - // <span className="text-sm"> - // {acceptedAt ? formatDateTime(acceptedAt) : "미승인"} - // </span> - // </div> - // ); - // }, - // enableSorting: true, - // enableHiding: true, - // }, - { - accessorKey: "dueDate", - header: ({ column }) => ( - <DataTableColumnHeaderSimple 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: "rejectionReason", - // header: ({ column }) => ( - // <DataTableColumnHeaderSimple column={column} title="반려사유" /> - // ), - // cell: ({ row }) => { - // const rejectionReason = row.getValue("rejectionReason") as string; - // return ( - // <div className="min-w-48 max-w-64"> - // {rejectionReason ? ( - // <TooltipProvider> - // <Tooltip> - // <TooltipTrigger asChild> - // <span className="truncate block text-sm text-red-600"> - // {rejectionReason} - // </span> - // </TooltipTrigger> - // <TooltipContent> - // <p className="max-w-xs">{rejectionReason}</p> - // </TooltipContent> - // </Tooltip> - // </TooltipProvider> - // ) : ( - // <span className="text-sm text-muted-foreground">N/A</span> - // )} - // </div> - // ); - // }, - // enableSorting: false, - // enableHiding: true, - // }, - { - accessorKey: "createdAt", - header: ({ column }) => ( - <DataTableColumnHeaderSimple 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 }) => ( - <DataTableColumnHeaderSimple 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, - }, - // { - // accessorKey: "createdByName", - // header: ({ column }) => ( - // <DataTableColumnHeaderSimple column={column} title="생성자" /> - // ), - // cell: ({ row }) => { - // const createdByName = row.getValue("createdByName") as string; - // return ( - // <div className="w-24"> - // <span className="text-sm">{createdByName || "N/A"}</span> - // </div> - // ); - // }, - // enableSorting: true, - // enableHiding: true, - // }, - // { - // accessorKey: "updatedByName", - // header: ({ column }) => ( - // <DataTableColumnHeaderSimple column={column} title="수정자" /> - // ), - // cell: ({ row }) => { - // const updatedByName = row.getValue("updatedByName") as string; - // return ( - // <div className="w-24"> - // <span className="text-sm">{updatedByName || "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} 견적서 작성`; - const isRejected = quotation.status === "Rejected"; - const isAccepted = quotation.status === "Accepted"; - const isDisabled = isRejected || isAccepted; - - return ( - <div className="w-16"> - <TooltipProvider> - <Tooltip> - <TooltipTrigger asChild> - <Button - variant="ghost" - size="icon" - onClick={() => { - if (!isDisabled) { - router.push(`/ko/partners/techsales/rfq-ship/${quotation.id}`); - } - }} - className="h-8 w-8" - disabled={isDisabled} - > - <Edit className="h-4 w-4" /> - <span className="sr-only">견적서 작성</span> - </Button> - </TooltipTrigger> - <TooltipContent> - <p>{isRejected ? "거절된 견적서는 편집할 수 없습니다" : isAccepted ? "승인된 견적서는 편집할 수 없습니다" : tooltipText}</p> - </TooltipContent> - </Tooltip> - </TooltipProvider> - </div> - ); - }, - enableSorting: false, - enableHiding: false, - }, - ]; +"use client"
+
+import * as React from "react"
+import { type ColumnDef } from "@tanstack/react-table"
+import { Edit, Paperclip, Package, Users } 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 {
+ TechSalesVendorQuotations,
+ TECH_SALES_QUOTATION_STATUS_CONFIG,
+ TECH_SALES_QUOTATION_STATUSES
+} from "@/db/schema"
+import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+
+interface QuotationWithRfqCode extends TechSalesVendorQuotations {
+ // RFQ 관련 정보
+ rfqCode?: string;
+ materialCode?: string;
+ dueDate?: Date;
+ rfqStatus?: string;
+
+ // 아이템 정보
+ itemName?: string;
+ itemCount?: number;
+
+ // 프로젝트 정보
+ projNm?: string;
+ pspid?: string;
+ sector?: string;
+
+ // RFQ 정보
+ description?: string;
+
+ // 벤더 정보
+ vendorName?: string;
+ vendorCode?: string;
+
+ // 사용자 정보
+ createdByName?: string | null;
+ updatedByName?: string | null;
+
+ // 첨부파일 개수
+ attachmentCount?: number;
+}
+
+interface GetColumnsProps {
+ router: AppRouterInstance;
+ openAttachmentsSheet: (rfqId: number) => void;
+ openItemsDialog: (rfq: { id: number; rfqCode?: string; status?: string; rfqType?: "SHIP" | "TOP" | "HULL"; }) => void;
+ openContactsDialog: (quotationId: number, vendorName?: string) => void;
+}
+
+export function getColumns({ router, openAttachmentsSheet, openItemsDialog, openContactsDialog }: 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 }) => {
+ const isRejected = row.original.status === TECH_SALES_QUOTATION_STATUSES.REJECTED;
+ const isAccepted = row.original.status === TECH_SALES_QUOTATION_STATUSES.ACCEPTED;
+ const isDisabled = isRejected || isAccepted;
+
+ return (
+ <Checkbox
+ checked={row.getIsSelected()}
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
+ aria-label="행 선택"
+ className="translate-y-0.5"
+ disabled={isDisabled}
+ />
+ );
+ },
+ enableSorting: false,
+ enableHiding: false,
+ },
+ // {
+ // accessorKey: "id",
+ // header: ({ column }) => (
+ // <DataTableColumnHeaderSimple 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 }) => (
+ <DataTableColumnHeaderSimple 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: "vendorName",
+ // header: ({ column }) => (
+ // <DataTableColumnHeaderSimple column={column} title="벤더명" />
+ // ),
+ // cell: ({ row }) => {
+ // const vendorName = row.getValue("vendorName") as string;
+ // return (
+ // <div className="min-w-32">
+ // <span className="text-sm">{vendorName || "N/A"}</span>
+ // </div>
+ // );
+ // },
+ // enableSorting: true,
+ // enableHiding: false,
+ // },
+ // {
+ // accessorKey: "vendorCode",
+ // header: ({ column }) => (
+ // <DataTableColumnHeaderSimple column={column} title="벤더 코드" />
+ // ),
+ // cell: ({ row }) => {
+ // const vendorCode = row.getValue("vendorCode") as string;
+ // return (
+ // <div className="min-w-24">
+ // <span className="font-mono text-sm">{vendorCode || "N/A"}</span>
+ // </div>
+ // );
+ // },
+ // enableSorting: true,
+ // enableHiding: true,
+ // },
+ // {
+ // accessorKey: "materialCode",
+ // header: ({ column }) => (
+ // <DataTableColumnHeaderSimple 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 }) => (
+ // <DataTableColumnHeaderSimple 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: true,
+ // enableHiding: true,
+ // },
+ {
+ accessorKey: "description",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="RFQ title" />
+ ),
+ cell: ({ row }) => {
+ const description = row.getValue("description") as string;
+ return (
+ <div className="min-w-48 max-w-64">
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <span className="truncate block text-sm">
+ {description || "N/A"}
+ </span>
+ </TooltipTrigger>
+ <TooltipContent>
+ <p className="max-w-xs">{description || "N/A"}</p>
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ </div>
+ );
+ },
+ enableSorting: true,
+ enableHiding: true,
+ },
+ {
+ accessorKey: "projNm",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple 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: true,
+ enableHiding: true,
+ },
+ // {
+ // accessorKey: "quotationCode",
+ // header: ({ column }) => (
+ // <DataTableColumnHeaderSimple column={column} title="견적서 번호" />
+ // ),
+ // cell: ({ row }) => {
+ // const quotationCode = row.getValue("quotationCode") as string;
+ // return (
+ // <div className="min-w-32">
+ // <span className="font-mono text-sm">{quotationCode || "미부여"}</span>
+ // </div>
+ // );
+ // },
+ // enableSorting: true,
+ // enableHiding: true,
+ // },
+ // {
+ // accessorKey: "quotationVersion",
+ // header: ({ column }) => (
+ // <DataTableColumnHeaderSimple column={column} title="버전" />
+ // ),
+ // cell: ({ row }) => {
+ // const quotationVersion = row.getValue("quotationVersion") as number;
+ // return (
+ // <div className="w-16 text-center">
+ // <span className="text-sm">{quotationVersion || 1}</span>
+ // </div>
+ // );
+ // },
+ // enableSorting: true,
+ // enableHiding: true,
+ // },
+ {
+ id: "items",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="아이템" />
+ ),
+ cell: ({ row }) => {
+ const quotation = row.original
+ const itemCount = quotation.itemCount || 0
+
+ const handleClick = () => {
+ const rfq = {
+ id: quotation.rfqId,
+ rfqCode: quotation.rfqCode,
+ status: quotation.rfqStatus,
+ rfqType: "SHIP" as const, // 기본값
+ }
+ openItemsDialog(rfq)
+ }
+
+ return (
+ <div className="w-20">
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="ghost"
+ size="sm"
+ className="relative h-8 w-8 p-0 group"
+ onClick={handleClick}
+ aria-label={`View ${itemCount} items`}
+ >
+ <Package className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" />
+ {itemCount > 0 && (
+ <span className="pointer-events-none absolute -top-1 -right-1 inline-flex h-4 min-w-[1rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-medium leading-none text-primary-foreground">
+ {itemCount}
+ </span>
+ )}
+ <span className="sr-only">
+ {itemCount > 0 ? `${itemCount} 아이템` : "아이템 없음"}
+ </span>
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>
+ <p>{itemCount > 0 ? `${itemCount}개 아이템 보기` : "아이템 없음"}</p>
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ </div>
+ )
+ },
+ enableSorting: false,
+ enableHiding: true,
+ },
+ {
+ id: "attachments",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="첨부파일" />
+ ),
+ cell: ({ row }) => {
+ const quotation = row.original
+ const attachmentCount = quotation.attachmentCount || 0
+ const handleClick = () => {
+ openAttachmentsSheet(quotation.rfqId)
+ }
+
+ return (
+ <div className="w-20">
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="ghost"
+ size="sm"
+ className="relative h-8 w-8 p-0 group"
+ onClick={handleClick}
+ aria-label={
+ attachmentCount > 0 ? `View ${attachmentCount} attachments` : "No attachments"
+ }
+ >
+ <Paperclip className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" />
+ {attachmentCount > 0 && (
+ <span className="pointer-events-none absolute -top-1 -right-1 inline-flex h-4 min-w-[1rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-medium leading-none text-primary-foreground">
+ {attachmentCount}
+ </span>
+ )}
+ <span className="sr-only">
+ {attachmentCount > 0 ? `${attachmentCount} 첨부파일` : "첨부파일 없음"}
+ </span>
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>
+ <p>{attachmentCount > 0 ? `${attachmentCount}개 첨부파일 보기` : "첨부파일 없음"}</p>
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ </div>
+ )
+ },
+ enableSorting: false,
+ enableHiding: true,
+ },
+ {
+ accessorKey: "status",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple 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 }) => (
+ <DataTableColumnHeaderSimple 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 }) => (
+ <DataTableColumnHeaderSimple 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 }) => (
+ <DataTableColumnHeaderSimple 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 }) => (
+ <DataTableColumnHeaderSimple 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: "acceptedAt",
+ // header: ({ column }) => (
+ // <DataTableColumnHeaderSimple column={column} title="승인일" />
+ // ),
+ // cell: ({ row }) => {
+ // const acceptedAt = row.getValue("acceptedAt") as Date;
+ // return (
+ // <div className="w-36">
+ // <span className="text-sm">
+ // {acceptedAt ? formatDateTime(acceptedAt) : "미승인"}
+ // </span>
+ // </div>
+ // );
+ // },
+ // enableSorting: true,
+ // enableHiding: true,
+ // },
+ {
+ accessorKey: "dueDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple 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: "rejectionReason",
+ // header: ({ column }) => (
+ // <DataTableColumnHeaderSimple column={column} title="반려사유" />
+ // ),
+ // cell: ({ row }) => {
+ // const rejectionReason = row.getValue("rejectionReason") as string;
+ // return (
+ // <div className="min-w-48 max-w-64">
+ // {rejectionReason ? (
+ // <TooltipProvider>
+ // <Tooltip>
+ // <TooltipTrigger asChild>
+ // <span className="truncate block text-sm text-red-600">
+ // {rejectionReason}
+ // </span>
+ // </TooltipTrigger>
+ // <TooltipContent>
+ // <p className="max-w-xs">{rejectionReason}</p>
+ // </TooltipContent>
+ // </Tooltip>
+ // </TooltipProvider>
+ // ) : (
+ // <span className="text-sm text-muted-foreground">N/A</span>
+ // )}
+ // </div>
+ // );
+ // },
+ // enableSorting: false,
+ // enableHiding: true,
+ // },
+ {
+ accessorKey: "createdAt",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple 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 }) => (
+ <DataTableColumnHeaderSimple 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,
+ },
+ // {
+ // accessorKey: "createdByName",
+ // header: ({ column }) => (
+ // <DataTableColumnHeaderSimple column={column} title="생성자" />
+ // ),
+ // cell: ({ row }) => {
+ // const createdByName = row.getValue("createdByName") as string;
+ // return (
+ // <div className="w-24">
+ // <span className="text-sm">{createdByName || "N/A"}</span>
+ // </div>
+ // );
+ // },
+ // enableSorting: true,
+ // enableHiding: true,
+ // },
+ // {
+ // accessorKey: "updatedByName",
+ // header: ({ column }) => (
+ // <DataTableColumnHeaderSimple column={column} title="수정자" />
+ // ),
+ // cell: ({ row }) => {
+ // const updatedByName = row.getValue("updatedByName") as string;
+ // return (
+ // <div className="w-24">
+ // <span className="text-sm">{updatedByName || "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} 견적서 작성`;
+ const isRejected = quotation.status === "Rejected";
+ const isAccepted = quotation.status === "Accepted";
+ const isDisabled = isRejected || isAccepted;
+
+ return (
+ <div className="w-16">
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="ghost"
+ size="icon"
+ onClick={() => {
+ if (!isDisabled) {
+ router.push(`/ko/partners/techsales/rfq-ship/${quotation.id}`);
+ }
+ }}
+ className="h-8 w-8"
+ disabled={isDisabled}
+ >
+ <Edit className="h-4 w-4" />
+ <span className="sr-only">견적서 작성</span>
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>
+ <p>{isRejected ? "거절된 견적서는 편집할 수 없습니다" : isAccepted ? "승인된 견적서는 편집할 수 없습니다" : tooltipText}</p>
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ </div>
+ );
+ },
+ enableSorting: false,
+ enableHiding: false,
+ },
+ {
+ id: "contacts",
+ header: "담당자",
+ cell: ({ row }) => {
+ const quotation = row.original;
+
+ const handleClick = () => {
+ openContactsDialog(quotation.id, quotation.vendorName);
+ };
+
+ return (
+ <div className="w-20">
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-8 w-8 p-0 group"
+ onClick={handleClick}
+ aria-label="담당자 정보 보기"
+ >
+ <Users className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" />
+ <span className="sr-only">담당자 정보 보기</span>
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>
+ <p>RFQ 발송 담당자 보기</p>
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ </div>
+ );
+ },
+ enableSorting: false,
+ enableHiding: true,
+ },
+ ];
}
\ No newline at end of file |
