summaryrefslogtreecommitdiff
path: root/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-07-21 07:54:26 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-07-21 07:54:26 +0000
commit14f61e24947fb92dd71ec0a7196a6e815f8e66da (patch)
tree317c501d64662d05914330628f867467fba78132 /lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx
parent194bd4bd7e6144d5c09c5e3f5476d254234dce72 (diff)
(최겸)기술영업 RFQ 담당자 초대, 요구사항 반영
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.tsx1380
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