diff options
Diffstat (limited to 'lib/techsales-rfq/table/detail-table')
| -rw-r--r-- | lib/techsales-rfq/table/detail-table/delete-vendors-dialog.tsx (renamed from lib/techsales-rfq/table/detail-table/delete-vendor-dialog.tsx) | 0 | ||||
| -rw-r--r-- | lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx | 70 | ||||
| -rw-r--r-- | lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx | 128 |
3 files changed, 129 insertions, 69 deletions
diff --git a/lib/techsales-rfq/table/detail-table/delete-vendor-dialog.tsx b/lib/techsales-rfq/table/detail-table/delete-vendors-dialog.tsx index d7e3403b..d7e3403b 100644 --- a/lib/techsales-rfq/table/detail-table/delete-vendor-dialog.tsx +++ b/lib/techsales-rfq/table/detail-table/delete-vendors-dialog.tsx 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 cfae0bd7..7d5c359e 100644 --- a/lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx +++ b/lib/techsales-rfq/table/detail-table/rfq-detail-column.tsx @@ -4,21 +4,20 @@ 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 } 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 { Checkbox } from "@/components/ui/checkbox"; -import { Ellipsis, MessageCircle } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; -import { useRouter } from "next/navigation"; export interface DataTableRowAction<TData> { row: Row<TData>; - type: "delete" | "communicate"; + type: "communicate" | "delete"; } // 벤더 견적 데이터 타입 정의 @@ -128,7 +127,28 @@ export function getRfqDetailColumns({ header: ({ column }) => ( <DataTableColumnHeaderSimple column={column} title="벤더명" /> ), - cell: ({ row }) => <div>{row.getValue("vendorName")}</div>, + 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/vendors/${vendorId}/info`, '_blank'); + }} + > + {vendorName} + </Button> + ); + } + + return <div>{vendorName}</div>; + }, meta: { excelHeader: "벤더명" }, @@ -233,13 +253,8 @@ export function getRfqDetailColumns({ cell: function Cell({ row }) { const vendorId = row.original.vendorId; const unreadCount = vendorId ? unreadMessages[vendorId] || 0 : 0; - const router = useRouter(); - - const handleViewDetails = () => { - if (vendorId) { - router.push(`/ko/evcp/vendors/${vendorId}/info`); - } - }; + const status = row.original.status; + const isDraft = status === "Draft"; return ( <div className="text-right flex items-center justify-end gap-1"> @@ -264,31 +279,26 @@ export function getRfqDetailColumns({ )} </div> - {/* 기존 드롭다운 메뉴 */} + {/* 컨텍스트 메뉴 */} <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="ghost" - className="flex h-8 w-8 p-0 data-[state=open]:bg-muted" + size="sm" + className="h-8 w-8 p-0" + title="더 많은 작업" > - <Ellipsis className="h-4 w-4" /> - <span className="sr-only">메뉴 열기</span> + <MoreHorizontal className="h-4 w-4" /> </Button> </DropdownMenuTrigger> - <DropdownMenuContent align="end" className="w-[160px]"> - <DropdownMenuItem - onClick={handleViewDetails} - disabled={!vendorId} - className="gap-2" - > - {/* <Eye className="h-4 w-4" /> */} - 벤더 상세정보 - </DropdownMenuItem> + <DropdownMenuContent align="end"> <DropdownMenuItem onClick={() => setRowAction({ row, type: "delete" })} - className="text-destructive focus:text-destructive" + 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> @@ -296,7 +306,7 @@ export function getRfqDetailColumns({ ); }, enableResizing: false, - size: 80, + size: 120, }, ]; }
\ No newline at end of file diff --git a/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx b/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx index a2f012ad..dbaeae0c 100644 --- a/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx +++ b/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx @@ -15,7 +15,7 @@ import { Button } from "@/components/ui/button" import { Loader2, UserPlus, BarChart2, Send, Trash2 } from "lucide-react" import { ClientDataTable } from "@/components/client-data-table/data-table" import { AddVendorDialog } from "./add-vendor-dialog" -import { DeleteVendorDialog } from "./delete-vendor-dialog" +import { DeleteVendorsDialog } from "./delete-vendors-dialog" import { VendorCommunicationDrawer } from "./vendor-communication-drawer" import { VendorQuotationComparisonDialog } from "./vendor-quotation-comparison-dialog" @@ -48,8 +48,6 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps const [isLoading, setIsLoading] = useState(false) const [details, setDetails] = useState<RfqDetailView[]>([]) const [vendorDialogOpen, setVendorDialogOpen] = React.useState(false) - const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false) - const [selectedDetail, setSelectedDetail] = React.useState<RfqDetailView | null>(null) const [isAdddialogLoading, setIsAdddialogLoading] = useState(false) @@ -70,6 +68,9 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps const [isSendingRfq, setIsSendingRfq] = useState(false) const [isDeletingVendors, setIsDeletingVendors] = useState(false) + // 벤더 삭제 확인 다이얼로그 상태 추가 + const [deleteConfirmDialogOpen, setDeleteConfirmDialogOpen] = useState(false) + // selectedRfq ID 메모이제이션 (객체 참조 변경 방지) const selectedRfqId = useMemo(() => selectedRfq?.id, [selectedRfq?.id]) @@ -83,12 +84,13 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps if (!selectedRfqId) return; try { - // TODO: 기술영업용 읽지 않은 메시지 수 가져오기 함수 구현 필요 - // const unreadData = await fetchUnreadMessages(selectedRfqId); - // setUnreadMessages(unreadData); - setUnreadMessages({}); + // 기술영업용 읽지 않은 메시지 수 가져오기 함수 구현 + const { getTechSalesUnreadMessageCounts } = await import("@/lib/techsales-rfq/service"); + const unreadData = await getTechSalesUnreadMessageCounts(selectedRfqId); + setUnreadMessages(unreadData); } catch (error) { console.error("읽지 않은 메시지 로드 오류:", error); + setUnreadMessages({}); } }, [selectedRfqId]); @@ -236,6 +238,21 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps } }, [selectedRows, selectedRfqId, handleRefreshData]); + // 벤더 삭제 확인 핸들러 + const handleDeleteVendorsConfirm = useCallback(() => { + if (selectedRows.length === 0) { + toast.warning("삭제할 벤더를 선택해주세요."); + return; + } + setDeleteConfirmDialogOpen(true); + }, [selectedRows]); + + // 벤더 삭제 확정 실행 + const executeDeleteVendors = useCallback(async () => { + setDeleteConfirmDialogOpen(false); + await handleDeleteVendors(); + }, [handleDeleteVendors]); + // 견적 비교 다이얼로그 열기 핸들러 메모이제이션 const handleOpenComparisonDialog = useCallback(() => { // 제출된 견적이 있는 벤더가 최소 1개 이상 있는지 확인 @@ -281,11 +298,6 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps ) // 계산된 값들 메모이제이션 - const totalUnreadMessages = useMemo(() => - Object.values(unreadMessages).reduce((sum, count) => sum + count, 0), - [unreadMessages] - ); - const vendorsWithQuotations = useMemo(() => details.filter(detail => detail.status === "Submitted").length, [details] @@ -360,24 +372,45 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps setSelectedVendor(rowAction.row.original); setCommunicationDrawerOpen(true); - // 해당 벤더의 읽지 않은 메시지를 0으로 설정 (메시지를 읽은 것으로 간주) - const vendorId = rowAction.row.original.vendorId; - if (vendorId) { - setUnreadMessages(prev => ({ - ...prev, - [vendorId]: 0 - })); - } - // rowAction 초기화 setRowAction(null); return; } - // 삭제 액션인 경우 + // 삭제 액션인 경우 개별 벤더 삭제 if (rowAction.type === "delete") { - setSelectedDetail(rowAction.row.original); - setDeleteDialogOpen(true); + const vendor = rowAction.row.original; + + if (!vendor.vendorId || !selectedRfqId) { + toast.error("벤더 정보가 없습니다."); + setRowAction(null); + return; + } + + // Draft 상태 체크 + if (vendor.status !== "Draft") { + toast.error("Draft 상태의 벤더만 삭제할 수 있습니다."); + setRowAction(null); + return; + } + + // 개별 벤더 삭제 + const { removeVendorFromTechSalesRfq } = await import("@/lib/techsales-rfq/service"); + + const result = await removeVendorFromTechSalesRfq({ + rfqId: selectedRfqId, + vendorId: vendor.vendorId + }); + + if (result.error) { + toast.error(result.error); + } else { + toast.success(`${vendor.vendorName || '벤더'}가 성공적으로 삭제되었습니다.`); + // 데이터 새로고침 + await handleRefreshData(); + } + + // rowAction 초기화 setRowAction(null); return; } @@ -388,7 +421,7 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps }; handleRowAction(); - }, [rowAction]) + }, [rowAction, selectedRfqId, handleRefreshData]) // 선택된 행 변경 핸들러 메모이제이션 const handleSelectedRowsChange = useCallback((selectedRowsData: RfqDetailView[]) => { @@ -398,9 +431,25 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps // 커뮤니케이션 드로어 변경 핸들러 메모이제이션 const handleCommunicationDrawerChange = useCallback((open: boolean) => { setCommunicationDrawerOpen(open); - // 드로어가 닫힐 때 읽지 않은 메시지 개수 갱신 - if (!open) loadUnreadMessages(); - }, [loadUnreadMessages]); + // 드로어가 닫힐 때 해당 벤더의 메시지를 읽음 처리하고 읽지 않은 메시지 개수 갱신 + if (!open && selectedVendor?.vendorId && selectedRfqId) { + // 메시지를 읽음으로 처리 + import("@/lib/techsales-rfq/service").then(({ markTechSalesMessagesAsRead }) => { + markTechSalesMessagesAsRead(selectedRfqId, selectedVendor.vendorId || undefined).catch(error => { + console.error("메시지 읽음 처리 오류:", error); + }); + }); + + // 해당 벤더의 읽지 않은 메시지를 0으로 즉시 업데이트 + setUnreadMessages(prev => ({ + ...prev, + [selectedVendor.vendorId!]: 0 + })); + + // 전체 읽지 않은 메시지 개수 갱신 + loadUnreadMessages(); + } + }, [selectedVendor, selectedRfqId, loadUnreadMessages]); if (!selectedRfq) { return ( @@ -439,11 +488,11 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps {selectedRows.length}개 선택됨 </Badge> )} - {totalUnreadMessages > 0 && ( + {/* {totalUnreadMessages > 0 && ( <Badge variant="destructive" className="h-6"> 읽지 않은 메시지: {totalUnreadMessages}건 </Badge> - )} + )} */} {vendorsWithQuotations > 0 && ( <Badge variant="outline" className="h-6"> 견적 제출: {vendorsWithQuotations}개 벤더 @@ -471,7 +520,7 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps <Button variant="outline" size="sm" - onClick={handleDeleteVendors} + onClick={handleDeleteVendorsConfirm} disabled={selectedRows.length === 0 || isDeletingVendors} className="gap-2" > @@ -549,14 +598,6 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps onSuccess={handleRefreshData} /> - <DeleteVendorDialog - open={deleteDialogOpen} - onOpenChange={setDeleteDialogOpen} - detail={selectedDetail} - showTrigger={false} - onSuccess={handleRefreshData} - /> - {/* 벤더 커뮤니케이션 드로어 */} <VendorCommunicationDrawer open={communicationDrawerOpen} @@ -572,6 +613,15 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps onOpenChange={setComparisonDialogOpen} selectedRfq={selectedRfq} /> + + {/* 다중 벤더 삭제 확인 다이얼로그 */} + <DeleteVendorsDialog + open={deleteConfirmDialogOpen} + onOpenChange={setDeleteConfirmDialogOpen} + vendors={selectedRows} + onConfirm={executeDeleteVendors} + isLoading={isDeletingVendors} + /> </div> ) }
\ No newline at end of file |
