diff options
Diffstat (limited to 'lib/techsales-rfq/vendor-response/table')
| -rw-r--r-- | lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx | 55 | ||||
| -rw-r--r-- | lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx | 72 |
2 files changed, 122 insertions, 5 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 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<QuotationWithRfqCode>[] { +export function getColumns({ router, openAttachmentsSheet }: GetColumnsProps): ColumnDef<QuotationWithRfqCode>[] { return [ { id: "select", @@ -163,6 +165,55 @@ export function getColumns({ router }: GetColumnsProps): ColumnDef<QuotationWith enableHiding: true, }, { + id: "attachments", + header: ({ column }) => ( + <DataTableColumnHeader 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 }) => ( <DataTableColumnHeader column={column} title="상태" /> 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<ExistingTechSalesAttachment[]>([]) // 데이터 안정성을 위한 메모이제이션 - 핵심 속성만 비교 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<DataTableFilterField<QuotationWithRfqCode>[]>(() => [ @@ -138,6 +194,16 @@ export function VendorQuotationsTable({ promises }: VendorQuotationsTableProps) </DataTableAdvancedToolbar> </DataTable> </div> + + {/* 첨부파일 관리 시트 (읽기 전용) */} + <TechSalesRfqAttachmentsSheet + open={attachmentsOpen} + onOpenChange={setAttachmentsOpen} + defaultAttachments={attachmentsDefault} + rfq={selectedRfqForAttachments} + onAttachmentsUpdated={() => {}} // 읽기 전용이므로 빈 함수 + readOnly={true} // 벤더 쪽에서는 항상 읽기 전용 + /> </div> ); }
\ No newline at end of file |
