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 | 71 | ||||
| -rw-r--r-- | lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx | 56 |
2 files changed, 92 insertions, 35 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 cf1dac42..ddee2317 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, Paperclip } from "lucide-react" +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" @@ -29,7 +29,8 @@ interface QuotationWithRfqCode extends TechSalesVendorQuotations { // 아이템 정보 itemName?: string; - itemShipbuildingId?: number; + + itemCount?: number; // 프로젝트 정보 projNm?: string; @@ -44,14 +45,6 @@ interface QuotationWithRfqCode extends TechSalesVendorQuotations { createdByName?: string | null; updatedByName?: string | null; - // 견적 코드 및 버전 - quotationCode?: string | null; - quotationVersion?: number | null; - - // 추가 상태 정보 - rejectionReason?: string | null; - acceptedAt?: Date | null; - // 첨부파일 개수 attachmentCount?: number; } @@ -59,9 +52,10 @@ interface QuotationWithRfqCode extends TechSalesVendorQuotations { 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 }: GetColumnsProps): ColumnDef<QuotationWithRfqCode>[] { +export function getColumns({ router, openAttachmentsSheet, openItemsDialog }: GetColumnsProps): ColumnDef<QuotationWithRfqCode>[] { return [ { id: "select", @@ -151,7 +145,7 @@ export function getColumns({ router, openAttachmentsSheet }: GetColumnsProps): C // { // accessorKey: "materialCode", // header: ({ column }) => ( - // <DataTableColumnHeaderSimple column={column} title="자재 코드" /> + // <DataTableColumnHeaderSimple column={column} title="자재 그룹" /> // ), // cell: ({ row }) => { // const materialCode = row.getValue("materialCode") as string; @@ -251,6 +245,59 @@ export function getColumns({ router, openAttachmentsSheet }: GetColumnsProps): C // 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="첨부파일" /> 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 e98d6bdc..55dcad92 100644 --- a/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx +++ b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx @@ -11,6 +11,7 @@ import { TechSalesVendorQuotations, TECH_SALES_QUOTATION_STATUSES, TECH_SALES_QU import { useRouter } from "next/navigation" import { getColumns } from "./vendor-quotations-table-columns" import { TechSalesRfqAttachmentsSheet, ExistingTechSalesAttachment } from "../../table/tech-sales-rfq-attachments-sheet" +import { RfqItemsViewDialog } from "../../table/rfq-items-view-dialog" import { getTechSalesRfqAttachments, getVendorQuotations } from "@/lib/techsales-rfq/service" import { toast } from "sonner" import { Skeleton } from "@/components/ui/skeleton" @@ -23,14 +24,16 @@ interface QuotationWithRfqCode extends TechSalesVendorQuotations { itemName?: string | null; projNm?: string | null; quotationCode?: string | null; - quotationVersion?: number | null; + rejectionReason?: string | null; acceptedAt?: Date | null; attachmentCount?: number; + itemCount?: number; } interface VendorQuotationsTableProps { vendorId: string; + rfqType?: "SHIP" | "TOP" | "HULL"; } // 로딩 스켈레톤 컴포넌트 @@ -92,22 +95,9 @@ function TableLoadingSkeleton() { ) } -// 중앙 로딩 인디케이터 컴포넌트 -function CenterLoadingIndicator() { - return ( - <div className="flex flex-col items-center justify-center py-12 space-y-4"> - <div className="relative"> - <div className="w-12 h-12 border-4 border-gray-200 border-t-blue-600 rounded-full animate-spin"></div> - </div> - <div className="text-center space-y-1"> - <p className="text-sm font-medium text-gray-900">데이터를 불러오는 중...</p> - <p className="text-xs text-gray-500">잠시만 기다려주세요.</p> - </div> - </div> - ) -} -export function VendorQuotationsTable({ vendorId }: VendorQuotationsTableProps) { + +export function VendorQuotationsTable({ vendorId, rfqType }: VendorQuotationsTableProps) { const searchParams = useSearchParams() const router = useRouter() @@ -116,6 +106,10 @@ export function VendorQuotationsTable({ vendorId }: VendorQuotationsTableProps) const [selectedRfqForAttachments, setSelectedRfqForAttachments] = React.useState<{ id: number; rfqCode: string | null; status: string } | null>(null) const [attachmentsDefault, setAttachmentsDefault] = React.useState<ExistingTechSalesAttachment[]>([]) + // 아이템 다이얼로그 상태 + const [itemsDialogOpen, setItemsDialogOpen] = React.useState(false) + const [selectedRfqForItems, setSelectedRfqForItems] = React.useState<{ id: number; rfqCode?: string; status?: string; rfqType?: "SHIP" | "TOP" | "HULL"; } | null>(null) + // 데이터 로딩 상태 const [data, setData] = React.useState<QuotationWithRfqCode[]>([]) const [pageCount, setPageCount] = React.useState(0) @@ -158,6 +152,7 @@ export function VendorQuotationsTable({ vendorId }: VendorQuotationsTableProps) search: initialSettings.search, from: initialSettings.from, to: initialSettings.to, + rfqType: rfqType, }, vendorId) console.log('🔍 [VendorQuotationsTable] 데이터 로드 결과:', { @@ -176,7 +171,7 @@ export function VendorQuotationsTable({ vendorId }: VendorQuotationsTableProps) setIsLoading(false) setIsInitialLoad(false) } - }, [vendorId, initialSettings]) + }, [vendorId, initialSettings, rfqType]) // URL 파라미터 변경 감지 및 데이터 재로드 (초기 로드 포함) React.useEffect(() => { @@ -192,8 +187,9 @@ export function VendorQuotationsTable({ vendorId }: VendorQuotationsTableProps) searchParams?.get('search'), searchParams?.get('from'), searchParams?.get('to'), - // vendorId 변경도 감지 - vendorId + // vendorId와 rfqType 변경도 감지 + vendorId, + rfqType ]) // 데이터 안정성을 위한 메모이제이션 @@ -246,12 +242,19 @@ export function VendorQuotationsTable({ vendorId }: VendorQuotationsTableProps) toast.error("첨부파일 조회 중 오류가 발생했습니다.") } }, [data]) + + // 아이템 다이얼로그 열기 함수 + const openItemsDialog = React.useCallback((rfq: { id: number; rfqCode?: string; status?: string; rfqType?: "SHIP" | "TOP" | "HULL"; }) => { + setSelectedRfqForItems(rfq) + setItemsDialogOpen(true) + }, []) // 테이블 컬럼 정의 const columns = React.useMemo(() => getColumns({ router, openAttachmentsSheet, - }), [router, openAttachmentsSheet]) + openItemsDialog, + }), [router, openAttachmentsSheet, openItemsDialog]) // 필터 필드 const filterFields = React.useMemo<DataTableFilterField<QuotationWithRfqCode>[]>(() => [ @@ -270,8 +273,8 @@ export function VendorQuotationsTable({ vendorId }: VendorQuotationsTableProps) }, { id: "materialCode", - label: "자재 코드", - placeholder: "자재 코드 검색...", + label: "자재 그룹", + placeholder: "자재 그룹 검색...", } ], []) @@ -284,7 +287,7 @@ export function VendorQuotationsTable({ vendorId }: VendorQuotationsTableProps) }, { id: "materialCode", - label: "자재 코드", + label: "자재 그룹", type: "text", }, { @@ -383,6 +386,13 @@ export function VendorQuotationsTable({ vendorId }: VendorQuotationsTableProps) onAttachmentsUpdated={() => {}} // 읽기 전용이므로 빈 함수 readOnly={true} // 벤더 쪽에서는 항상 읽기 전용 /> + + {/* 아이템 보기 다이얼로그 */} + <RfqItemsViewDialog + open={itemsDialogOpen} + onOpenChange={setItemsDialogOpen} + rfq={selectedRfqForItems} + /> </div> ); }
\ No newline at end of file |
