summaryrefslogtreecommitdiff
path: root/lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx')
-rw-r--r--lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx850
1 files changed, 450 insertions, 400 deletions
diff --git a/lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx b/lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx
index e921fcaa..e4141520 100644
--- a/lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx
+++ b/lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx
@@ -1,401 +1,451 @@
-"use client"
-
-import * as React from "react"
-import type { ColumnDef, Row } from "@tanstack/react-table";
-import { formatDate } from "@/lib/utils"
-import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
-import { Checkbox } from "@/components/ui/checkbox";
-import { MessageCircle, MoreHorizontal, Trash2, Paperclip } from "lucide-react";
-import { Button } from "@/components/ui/button";
-import { Badge } from "@/components/ui/badge";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-
-export interface DataTableRowAction<TData> {
- row: Row<TData>;
- type: "communicate" | "delete";
-}
-
-// 벤더 견적 데이터 타입 정의
-export interface RfqDetailView {
- id: number
- rfqId: number
- vendorId?: number | null
- vendorName: string | null
- vendorCode: string | null
- totalPrice: string | number | null
- currency: string | null
- validUntil: Date | null
- status: string | null
- remark: string | null
- submittedAt: Date | null
- acceptedAt: Date | null
- rejectionReason: string | null
- createdAt: Date | null
- updatedAt: Date | null
- createdByName: string | null
- quotationCode?: string | null
- rfqCode?: string | null
- quotationAttachments?: Array<{
- id: number
- revisionId: number
- fileName: string
- fileSize: number
- filePath: string
- description?: string | null
- }>
-}
-
-// 견적서 정보 타입 (Sheet용)
-export interface QuotationInfo {
- id: number
- quotationCode: string | null
- vendorName?: string
- rfqCode?: string
-}
-
-interface GetColumnsProps<TData> {
- setRowAction: React.Dispatch<
- React.SetStateAction<DataTableRowAction<TData> | null>
- >;
- unreadMessages?: Record<number, number>; // 읽지 않은 메시지 개수
- onQuotationClick?: (quotationId: number) => void; // 견적 클릭 핸들러
- openQuotationAttachmentsSheet?: (quotationId: number, quotationInfo: QuotationInfo) => void; // 견적서 첨부파일 sheet 열기
-}
-
-export function getRfqDetailColumns({
- setRowAction,
- unreadMessages = {},
- onQuotationClick,
- openQuotationAttachmentsSheet
-}: GetColumnsProps<RfqDetailView>): ColumnDef<RfqDetailView>[] {
- return [
- {
- id: "select",
- header: ({ table }) => (
- <Checkbox
- checked={
- table.getIsAllPageRowsSelected() ||
- (table.getIsSomePageRowsSelected() && "indeterminate")
- }
- onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
- aria-label="모두 선택"
- />
- ),
- cell: ({ row }) => {
- const status = row.original.status;
- const isSelectable = status ? !["Accepted", "Rejected"].includes(status) : true;
-
- return (
- <Checkbox
- checked={row.getIsSelected()}
- onCheckedChange={(value) => row.toggleSelected(!!value)}
- disabled={!isSelectable}
- aria-label="행 선택"
- className={!isSelectable ? "opacity-50 cursor-not-allowed" : ""}
- />
- );
- },
- enableSorting: false,
- enableHiding: false,
- size: 40,
- },
- {
- accessorKey: "status",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="견적 상태" />
- ),
- cell: ({ row }) => {
- const status = row.getValue("status") as string;
- // 상태에 따른 배지 색상 설정
- let variant: "default" | "secondary" | "outline" | "destructive" = "outline";
-
- if (status === "Submitted") {
- variant = "default"; // 제출됨 - 기본 색상
- } else if (status === "Accepted") {
- variant = "secondary"; // 승인됨 - 보조 색상
- } else if (status === "Rejected") {
- variant = "destructive"; // 거부됨 - 위험 색상
- }
-
- return (
- <Badge variant={variant}>{status || "Draft"}</Badge>
- );
- },
- meta: {
- excelHeader: "견적 상태"
- },
- enableResizing: true,
- size: 120,
- },
- {
- accessorKey: "vendorCode",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="벤더 코드" />
- ),
- cell: ({ row }) => <div>{row.getValue("vendorCode")}</div>,
- meta: {
- excelHeader: "벤더 코드"
- },
- enableResizing: true,
- size: 120,
- },
- {
- accessorKey: "vendorName",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="벤더명" />
- ),
- cell: ({ row }) => {
- const vendorName = row.getValue("vendorName") as string | null;
- const vendorId = row.original.vendorId;
-
- if (!vendorName) return <div>-</div>;
-
- if (vendorId) {
- return (
- <Button
- variant="link"
- className="p-0 h-auto font-normal text-left justify-start hover:underline"
- onClick={() => {
- window.open(`/ko/evcp/tech-vendors/${vendorId}/info`, '_blank');
- }}
- >
- {vendorName}
- </Button>
- );
- }
-
- return <div>{vendorName}</div>;
- },
- meta: {
- excelHeader: "벤더명"
- },
- enableResizing: true,
- size: 160,
- },
- {
- accessorKey: "totalPrice",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="견적 금액" />
- ),
- cell: ({ row }) => {
- const value = row.getValue("totalPrice") as string | number | null;
- const currency = row.getValue("currency") as string | null;
- const quotationId = row.original.id;
-
- if (value === null || value === undefined) return "-";
-
- // 숫자로 변환 시도
- const numValue = typeof value === 'string' ? parseFloat(value) : value;
- const displayValue = isNaN(numValue) ? value : numValue.toLocaleString();
-
- // 견적값이 있고 클릭 핸들러가 있는 경우 클릭 가능한 버튼으로 표시
- if (onQuotationClick && quotationId) {
- return (
- <Button
- variant="link"
- className="p-0 h-auto font-medium text-left justify-start hover:underline"
- onClick={() => onQuotationClick(quotationId)}
- title="견적 히스토리 보기"
- >
- {displayValue} {currency}
- </Button>
- );
- }
-
- return (
- <div className="font-medium">
- {displayValue} {currency}
- </div>
- );
- },
- meta: {
- excelHeader: "견적 금액"
- },
- enableResizing: true,
- size: 140,
- },
- {
- accessorKey: "quotationAttachments",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="첨부파일" />
- ),
- cell: ({ row }) => {
- const attachments = row.original.quotationAttachments || [];
- const attachmentCount = attachments.length;
-
- if (attachmentCount === 0) {
- return <div className="text-muted-foreground">-</div>;
- }
-
- return (
- <Button
- variant="ghost"
- size="sm"
- className="relative h-8 w-8 p-0 group"
- onClick={() => {
- // 견적서 첨부파일 sheet 열기
- if (openQuotationAttachmentsSheet) {
- const quotation = row.original;
- openQuotationAttachmentsSheet(quotation.id, {
- id: quotation.id,
- quotationCode: quotation.quotationCode || null,
- vendorName: quotation.vendorName || undefined,
- rfqCode: quotation.rfqCode || undefined,
- });
- }
- }}
- title={
- attachmentCount === 1
- ? `${attachments[0].fileName} (${(attachments[0].fileSize / 1024 / 1024).toFixed(2)} MB)`
- : `${attachmentCount}개의 첨부파일:\n${attachments.map(att => att.fileName).join('\n')}`
- }
- >
- <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>
- )}
- </Button>
- );
- },
- meta: {
- excelHeader: "첨부파일"
- },
- enableResizing: false,
- size: 80,
- },
- {
- accessorKey: "currency",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="통화" />
- ),
- cell: ({ row }) => <div>{row.getValue("currency")}</div>,
- meta: {
- excelHeader: "통화"
- },
- enableResizing: true,
- size: 80,
- },
- {
- accessorKey: "validUntil",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="유효기간" />
- ),
- cell: ({ cell }) => {
- const value = cell.getValue() as Date | null;
- return value ? formatDate(value, "KR") : "-";
- },
- meta: {
- excelHeader: "유효기간"
- },
- enableResizing: true,
- size: 120,
- },
- {
- accessorKey: "submittedAt",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="제출일" />
- ),
- cell: ({ cell }) => {
- const value = cell.getValue() as Date | null;
- return value ? formatDate(value, "KR") : "-";
- },
- meta: {
- excelHeader: "제출일"
- },
- enableResizing: true,
- size: 120,
- },
- {
- accessorKey: "createdByName",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="등록자" />
- ),
- cell: ({ row }) => <div>{row.getValue("createdByName")}</div>,
- meta: {
- excelHeader: "등록자"
- },
- enableResizing: true,
- size: 120,
- },
- {
- accessorKey: "remark",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="비고" />
- ),
- cell: ({ row }) => <div>{row.getValue("remark") || "-"}</div>,
- meta: {
- excelHeader: "비고"
- },
- enableResizing: true,
- size: 200,
- },
- {
- id: "actions",
- header: () => <div className="text-right">동작</div>,
- cell: function Cell({ row }) {
- const vendorId = row.original.vendorId;
- const unreadCount = vendorId ? unreadMessages[vendorId] || 0 : 0;
- const status = row.original.status;
- const isDraft = status === "Draft";
-
- return (
- <div className="text-right flex items-center justify-end gap-1">
- {/* 커뮤니케이션 버튼 */}
- <div className="relative">
- <Button
- variant="ghost"
- size="sm"
- className="h-8 w-8 p-0"
- onClick={() => setRowAction({ row, type: "communicate" })}
- title="벤더와 커뮤니케이션"
- >
- <MessageCircle className="h-4 w-4" />
- </Button>
- {unreadCount > 0 && (
- <Badge
- variant="destructive"
- className="absolute -top-1 -right-1 h-4 w-4 p-0 text-xs flex items-center justify-center"
- >
- {unreadCount > 9 ? '9+' : unreadCount}
- </Badge>
- )}
- </div>
-
- {/* 컨텍스트 메뉴 */}
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button
- variant="ghost"
- size="sm"
- className="h-8 w-8 p-0"
- title="더 많은 작업"
- >
- <MoreHorizontal className="h-4 w-4" />
- </Button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="end">
- <DropdownMenuItem
- onClick={() => setRowAction({ row, type: "delete" })}
- disabled={!isDraft}
- className={!isDraft ? "opacity-50 cursor-not-allowed" : "text-destructive focus:text-destructive"}
- >
- <Trash2 className="mr-2 h-4 w-4" />
- 벤더 삭제
- </DropdownMenuItem>
- </DropdownMenuContent>
- </DropdownMenu>
- </div>
- );
- },
- enableResizing: false,
- size: 120,
- },
- ];
+"use client"
+
+import * as React from "react"
+import type { ColumnDef, Row } from "@tanstack/react-table";
+import { formatDate } from "@/lib/utils"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import { Checkbox } from "@/components/ui/checkbox";
+import { MessageCircle, MoreHorizontal, Trash2, Paperclip, Users } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+
+export interface DataTableRowAction<TData> {
+ row: Row<TData>;
+ type: "communicate" | "delete";
+}
+
+// 벤더 견적 데이터 타입 정의
+export interface RfqDetailView {
+ id: number
+ rfqId: number
+ vendorId?: number | null
+ vendorName: string | null
+ vendorCode: string | null
+ totalPrice: string | number | null
+ currency: string | null
+ validUntil: Date | null
+ status: string | null
+ remark: string | null
+ submittedAt: Date | null
+ acceptedAt: Date | null
+ rejectionReason: string | null
+ createdAt: Date | null
+ updatedAt: Date | null
+ createdByName: string | null
+ quotationCode?: string | null
+ rfqCode?: string | null
+ quotationAttachments?: Array<{
+ id: number
+ revisionId: number
+ fileName: string
+ fileSize: number
+ filePath: string
+ description?: string | null
+ }>
+}
+
+// 견적서 정보 타입 (Sheet용)
+export interface QuotationInfo {
+ id: number
+ quotationCode: string | null
+ vendorName?: string
+ rfqCode?: string
+}
+
+interface GetColumnsProps<TData> {
+ setRowAction: React.Dispatch<
+ React.SetStateAction<DataTableRowAction<TData> | null>
+ >;
+ unreadMessages?: Record<number, number>; // 읽지 않은 메시지 개수
+ onQuotationClick?: (quotationId: number) => void; // 견적 클릭 핸들러
+ openQuotationAttachmentsSheet?: (quotationId: number, quotationInfo: QuotationInfo) => void; // 견적서 첨부파일 sheet 열기
+ openContactsDialog?: (quotationId: number, vendorName?: string) => void; // 담당자 조회 다이얼로그 열기
+}
+
+export function getRfqDetailColumns({
+ setRowAction,
+ unreadMessages = {},
+ onQuotationClick,
+ openQuotationAttachmentsSheet,
+ openContactsDialog
+}: GetColumnsProps<RfqDetailView>): ColumnDef<RfqDetailView>[] {
+ return [
+ {
+ id: "select",
+ header: ({ table }) => (
+ <Checkbox
+ checked={
+ table.getIsAllPageRowsSelected() ||
+ (table.getIsSomePageRowsSelected() && "indeterminate")
+ }
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
+ aria-label="모두 선택"
+ />
+ ),
+ cell: ({ row }) => {
+ const status = row.original.status;
+ const isSelectable = status ? !["Accepted", "Rejected"].includes(status) : true;
+
+ return (
+ <Checkbox
+ checked={row.getIsSelected()}
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
+ disabled={!isSelectable}
+ aria-label="행 선택"
+ className={!isSelectable ? "opacity-50 cursor-not-allowed" : ""}
+ />
+ );
+ },
+ enableSorting: false,
+ enableHiding: false,
+ size: 40,
+ },
+ {
+ accessorKey: "status",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="견적 상태" />
+ ),
+ cell: ({ row }) => {
+ const status = row.getValue("status") as string;
+ // 상태에 따른 배지 색상 설정
+ let variant: "default" | "secondary" | "outline" | "destructive" = "outline";
+
+ if (status === "Submitted") {
+ variant = "default"; // 제출됨 - 기본 색상
+ } else if (status === "Accepted") {
+ variant = "secondary"; // 승인됨 - 보조 색상
+ } else if (status === "Rejected") {
+ variant = "destructive"; // 거부됨 - 위험 색상
+ }
+
+ return (
+ <Badge variant={variant}>{status || "Draft"}</Badge>
+ );
+ },
+ meta: {
+ excelHeader: "견적 상태"
+ },
+ enableResizing: true,
+ size: 120,
+ },
+ {
+ accessorKey: "vendorCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="벤더 코드" />
+ ),
+ cell: ({ row }) => <div>{row.getValue("vendorCode")}</div>,
+ meta: {
+ excelHeader: "벤더 코드"
+ },
+ enableResizing: true,
+ size: 120,
+ },
+ {
+ accessorKey: "vendorName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="벤더명" />
+ ),
+ cell: ({ row }) => {
+ const vendorName = row.getValue("vendorName") as string | null;
+ const vendorId = row.original.vendorId;
+
+ if (!vendorName) return <div>-</div>;
+
+ if (vendorId) {
+ return (
+ <Button
+ variant="link"
+ className="p-0 h-auto font-normal text-left justify-start hover:underline"
+ onClick={() => {
+ window.open(`/ko/evcp/tech-vendors/${vendorId}/info`, '_blank');
+ }}
+ >
+ {vendorName}
+ </Button>
+ );
+ }
+
+ return <div>{vendorName}</div>;
+ },
+ meta: {
+ excelHeader: "벤더명"
+ },
+ enableResizing: true,
+ size: 160,
+ },
+ {
+ accessorKey: "totalPrice",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="견적 금액" />
+ ),
+ cell: ({ row }) => {
+ const value = row.getValue("totalPrice") as string | number | null;
+ const currency = row.getValue("currency") as string | null;
+ const quotationId = row.original.id;
+
+ if (value === null || value === undefined) return "-";
+
+ // 숫자로 변환 시도
+ const numValue = typeof value === 'string' ? parseFloat(value) : value;
+ const displayValue = isNaN(numValue) ? value : numValue.toLocaleString();
+
+ // 견적값이 있고 클릭 핸들러가 있는 경우 클릭 가능한 버튼으로 표시
+ if (onQuotationClick && quotationId) {
+ return (
+ <Button
+ variant="link"
+ className="p-0 h-auto font-medium text-left justify-start hover:underline"
+ onClick={() => onQuotationClick(quotationId)}
+ title="견적 히스토리 보기"
+ >
+ {displayValue} {currency}
+ </Button>
+ );
+ }
+
+ return (
+ <div className="font-medium">
+ {displayValue} {currency}
+ </div>
+ );
+ },
+ meta: {
+ excelHeader: "견적 금액"
+ },
+ enableResizing: true,
+ size: 140,
+ },
+ {
+ accessorKey: "quotationAttachments",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="첨부파일" />
+ ),
+ cell: ({ row }) => {
+ const attachments = row.original.quotationAttachments || [];
+ const attachmentCount = attachments.length;
+
+ if (attachmentCount === 0) {
+ return <div className="text-muted-foreground">-</div>;
+ }
+
+ return (
+ <Button
+ variant="ghost"
+ size="sm"
+ className="relative h-8 w-8 p-0 group"
+ onClick={() => {
+ // 견적서 첨부파일 sheet 열기
+ if (openQuotationAttachmentsSheet) {
+ const quotation = row.original;
+ openQuotationAttachmentsSheet(quotation.id, {
+ id: quotation.id,
+ quotationCode: quotation.quotationCode || null,
+ vendorName: quotation.vendorName || undefined,
+ rfqCode: quotation.rfqCode || undefined,
+ });
+ }
+ }}
+ title={
+ attachmentCount === 1
+ ? `${attachments[0].fileName} (${(attachments[0].fileSize / 1024 / 1024).toFixed(2)} MB)`
+ : `${attachmentCount}개의 첨부파일:\n${attachments.map(att => att.fileName).join('\n')}`
+ }
+ >
+ <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>
+ )}
+ </Button>
+ );
+ },
+ meta: {
+ excelHeader: "첨부파일"
+ },
+ enableResizing: false,
+ size: 80,
+ },
+ {
+ id: "contacts",
+ header: "담당자",
+ cell: ({ row }) => {
+ const quotation = row.original;
+
+ const handleClick = () => {
+ if (openContactsDialog) {
+ openContactsDialog(quotation.id, quotation.vendorName || undefined);
+ }
+ };
+
+ 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>
+ );
+ },
+ meta: {
+ excelHeader: "담당자"
+ },
+ enableResizing: false,
+ size: 80,
+ },
+ {
+ accessorKey: "currency",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="통화" />
+ ),
+ cell: ({ row }) => <div>{row.getValue("currency")}</div>,
+ meta: {
+ excelHeader: "통화"
+ },
+ enableResizing: true,
+ size: 80,
+ },
+ {
+ accessorKey: "validUntil",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="유효기간" />
+ ),
+ cell: ({ cell }) => {
+ const value = cell.getValue() as Date | null;
+ return value ? formatDate(value, "KR") : "-";
+ },
+ meta: {
+ excelHeader: "유효기간"
+ },
+ enableResizing: true,
+ size: 120,
+ },
+ {
+ accessorKey: "submittedAt",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="제출일" />
+ ),
+ cell: ({ cell }) => {
+ const value = cell.getValue() as Date | null;
+ return value ? formatDate(value, "KR") : "-";
+ },
+ meta: {
+ excelHeader: "제출일"
+ },
+ enableResizing: true,
+ size: 120,
+ },
+ {
+ accessorKey: "createdByName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="등록자" />
+ ),
+ cell: ({ row }) => <div>{row.getValue("createdByName")}</div>,
+ meta: {
+ excelHeader: "등록자"
+ },
+ enableResizing: true,
+ size: 120,
+ },
+ {
+ accessorKey: "remark",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="비고" />
+ ),
+ cell: ({ row }) => <div>{row.getValue("remark") || "-"}</div>,
+ meta: {
+ excelHeader: "비고"
+ },
+ enableResizing: true,
+ size: 200,
+ },
+ {
+ id: "actions",
+ header: () => <div className="text-right">동작</div>,
+ cell: function Cell({ row }) {
+ const vendorId = row.original.vendorId;
+ const unreadCount = vendorId ? unreadMessages[vendorId] || 0 : 0;
+ const status = row.original.status;
+ const isDraft = status === "Draft";
+
+ return (
+ <div className="text-right flex items-center justify-end gap-1">
+ {/* 커뮤니케이션 버튼 */}
+ <div className="relative">
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-8 w-8 p-0"
+ onClick={() => setRowAction({ row, type: "communicate" })}
+ title="벤더와 커뮤니케이션"
+ >
+ <MessageCircle className="h-4 w-4" />
+ </Button>
+ {unreadCount > 0 && (
+ <Badge
+ variant="destructive"
+ className="absolute -top-1 -right-1 h-4 w-4 p-0 text-xs flex items-center justify-center"
+ >
+ {unreadCount > 9 ? '9+' : unreadCount}
+ </Badge>
+ )}
+ </div>
+
+ {/* 컨텍스트 메뉴 */}
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-8 w-8 p-0"
+ title="더 많은 작업"
+ >
+ <MoreHorizontal className="h-4 w-4" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end">
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "delete" })}
+ disabled={!isDraft}
+ className={!isDraft ? "opacity-50 cursor-not-allowed" : "text-destructive focus:text-destructive"}
+ >
+ <Trash2 className="mr-2 h-4 w-4" />
+ 벤더 삭제
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ </div>
+ );
+ },
+ enableResizing: false,
+ size: 120,
+ },
+ ];
} \ No newline at end of file