diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-21 07:54:26 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-21 07:54:26 +0000 |
| commit | 14f61e24947fb92dd71ec0a7196a6e815f8e66da (patch) | |
| tree | 317c501d64662d05914330628f867467fba78132 /lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx | |
| parent | 194bd4bd7e6144d5c09c5e3f5476d254234dce72 (diff) | |
(최겸)기술영업 RFQ 담당자 초대, 요구사항 반영
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.tsx | 850 |
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 |
