From e484964b1d78cedabbe182c789a8e4c9b53e29d3 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Thu, 29 May 2025 05:12:36 +0000 Subject: (김준회) 기술영업 조선 RFQ 파일첨부 및 채팅 기능 구현 / menuConfig 수정 (벤더 기술영업) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table/vendor-quotations-table-columns.tsx | 55 ++++++++++++++++- .../table/vendor-quotations-table.tsx | 72 +++++++++++++++++++++- 2 files changed, 122 insertions(+), 5 deletions(-) (limited to 'lib/techsales-rfq/vendor-response/table') 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 5c6971cc..109698ea 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 @@ -2,7 +2,7 @@ import * as React from "react" import { type ColumnDef } from "@tanstack/react-table" -import { Edit } from "lucide-react" +import { Edit, Paperclip } from "lucide-react" import { formatCurrency, formatDate, formatDateTime } from "@/lib/utils" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" @@ -31,13 +31,15 @@ interface QuotationWithRfqCode extends TechSalesVendorQuotations { quotationVersion?: number | null; rejectionReason?: string | null; acceptedAt?: Date | null; + attachmentCount?: number; } interface GetColumnsProps { router: AppRouterInstance; + openAttachmentsSheet: (rfqId: number) => void; } -export function getColumns({ router }: GetColumnsProps): ColumnDef[] { +export function getColumns({ router, openAttachmentsSheet }: GetColumnsProps): ColumnDef[] { return [ { id: "select", @@ -162,6 +164,55 @@ export function getColumns({ router }: GetColumnsProps): ColumnDef ( + + ), + cell: ({ row }) => { + const quotation = row.original + const attachmentCount = quotation.attachmentCount || 0 + + const handleClick = () => { + openAttachmentsSheet(quotation.rfqId) + } + + return ( +
+ + + + + + +

{attachmentCount > 0 ? `${attachmentCount}개 첨부파일 보기` : "첨부파일 없음"}

+
+
+
+
+ ) + }, + enableSorting: false, + enableHiding: true, + }, { accessorKey: "status", header: ({ column }) => ( diff --git a/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx index 63d4674b..e1b82579 100644 --- a/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx +++ b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx @@ -9,6 +9,9 @@ import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-adv import { TechSalesVendorQuotations, TECH_SALES_QUOTATION_STATUSES, TECH_SALES_QUOTATION_STATUS_CONFIG } from "@/db/schema" import { useRouter } from "next/navigation" import { getColumns } from "./vendor-quotations-table-columns" +import { TechSalesRfqAttachmentsSheet, ExistingTechSalesAttachment } from "../../table/tech-sales-rfq-attachments-sheet" +import { getTechSalesRfqAttachments } from "@/lib/techsales-rfq/service" +import { toast } from "sonner" interface QuotationWithRfqCode extends TechSalesVendorQuotations { rfqCode?: string; @@ -18,13 +21,14 @@ interface QuotationWithRfqCode extends TechSalesVendorQuotations { itemName?: string; projNm?: string; quotationCode?: string | null; - quotationVersion?: number | null; + quotationVersion: number | null; rejectionReason?: string | null; acceptedAt?: Date | null; + attachmentCount?: number; } interface VendorQuotationsTableProps { - promises: Promise<[{ data: any[], pageCount: number, total?: number }]>; + promises: Promise<[{ data: QuotationWithRfqCode[], pageCount: number, total?: number }]>; } export function VendorQuotationsTable({ promises }: VendorQuotationsTableProps) { @@ -34,16 +38,68 @@ export function VendorQuotationsTable({ promises }: VendorQuotationsTableProps) const [{ data, pageCount }] = React.use(promises); const router = useRouter(); + + // 첨부파일 시트 상태 + const [attachmentsOpen, setAttachmentsOpen] = React.useState(false) + const [selectedRfqForAttachments, setSelectedRfqForAttachments] = React.useState<{ id: number; rfqCode: string | null; status: string } | null>(null) + const [attachmentsDefault, setAttachmentsDefault] = React.useState([]) // 데이터 안정성을 위한 메모이제이션 - 핵심 속성만 비교 const stableData = React.useMemo(() => { return data; }, [data.length, data.map(item => `${item.id}-${item.status}-${item.updatedAt}`).join(',')]); + // 첨부파일 시트 열기 함수 + const openAttachmentsSheet = React.useCallback(async (rfqId: number) => { + try { + // RFQ 정보 조회 (data에서 rfqId에 해당하는 데이터 찾기) + const quotationWithRfq = data.find(item => item.rfqId === rfqId) + if (!quotationWithRfq) { + toast.error("RFQ 정보를 찾을 수 없습니다.") + return + } + + // 실제 첨부파일 목록 조회 API 호출 + const result = await getTechSalesRfqAttachments(rfqId) + + if (result.error) { + toast.error(result.error) + return + } + + // API 응답을 ExistingTechSalesAttachment 형식으로 변환 + const attachments: ExistingTechSalesAttachment[] = result.data.map(att => ({ + id: att.id, + techSalesRfqId: att.techSalesRfqId || rfqId, + fileName: att.fileName, + originalFileName: att.originalFileName, + filePath: att.filePath, + fileSize: att.fileSize || undefined, + fileType: att.fileType || undefined, + attachmentType: att.attachmentType as "RFQ_COMMON" | "VENDOR_SPECIFIC", + description: att.description || undefined, + createdBy: att.createdBy, + createdAt: att.createdAt, + })) + + setAttachmentsDefault(attachments) + setSelectedRfqForAttachments({ + id: rfqId, + rfqCode: quotationWithRfq.rfqCode || null, + status: quotationWithRfq.rfqStatus || "Unknown" + }) + setAttachmentsOpen(true) + } catch (error) { + console.error("첨부파일 조회 오류:", error) + toast.error("첨부파일 조회 중 오류가 발생했습니다.") + } + }, [data]) + // 테이블 컬럼 정의 - router는 안정적이므로 한 번만 생성 const columns = React.useMemo(() => getColumns({ router, - }), [router]); + openAttachmentsSheet, + }), [router, openAttachmentsSheet]); // 필터 필드 - 중앙화된 상태 상수 사용 const filterFields = React.useMemo[]>(() => [ @@ -138,6 +194,16 @@ export function VendorQuotationsTable({ promises }: VendorQuotationsTableProps) + + {/* 첨부파일 관리 시트 (읽기 전용) */} + {}} // 읽기 전용이므로 빈 함수 + readOnly={true} // 벤더 쪽에서는 항상 읽기 전용 + /> ); } \ No newline at end of file -- cgit v1.2.3