summaryrefslogtreecommitdiff
path: root/lib/techsales-rfq/table/detail-table
diff options
context:
space:
mode:
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.tsx70
-rw-r--r--lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx128
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