summaryrefslogtreecommitdiff
path: root/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-06-24 01:51:59 +0000
committerjoonhoekim <26rote@gmail.com>2025-06-24 01:51:59 +0000
commit6824e097d768f724cf439b410ccfb1ab9685ac98 (patch)
tree1f297313637878e7a4ad6c89b84d5a2c3e9eb650 /lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx
parentf4825dd3853188de4688fb4a56c0f4e847da314b (diff)
parent4e63d8427d26d0d1b366ddc53650e15f3481fc75 (diff)
(merge) 대표님/최겸 작업사항 머지
Diffstat (limited to 'lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx')
-rw-r--r--lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx178
1 files changed, 134 insertions, 44 deletions
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 f2eda8d9..1d701bd5 100644
--- a/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx
+++ b/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx
@@ -12,12 +12,14 @@ import { toast } from "sonner"
import { Skeleton } from "@/components/ui/skeleton"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
-import { Loader2, UserPlus, BarChart2, Send, Trash2 } from "lucide-react"
+import { Loader2, UserPlus, Send, Trash2, CheckCircle } from "lucide-react"
import { ClientDataTable } from "@/components/client-data-table/data-table"
import { AddVendorDialog } from "./add-vendor-dialog"
import { VendorCommunicationDrawer } from "./vendor-communication-drawer"
-import { VendorQuotationComparisonDialog } from "./vendor-quotation-comparison-dialog"
import { DeleteVendorsDialog } from "../delete-vendors-dialog"
+import { QuotationHistoryDialog } from "@/lib/techsales-rfq/table/detail-table/quotation-history-dialog"
+import { TechSalesQuotationAttachmentsSheet, type QuotationAttachment } from "../tech-sales-quotation-attachments-sheet"
+import type { QuotationInfo } from "./rfq-detail-column"
// 기본적인 RFQ 타입 정의
interface TechSalesRfq {
@@ -30,6 +32,8 @@ interface TechSalesRfq {
rfqSendDate?: Date | null
dueDate?: Date | null
createdByName?: string | null
+ rfqType: "SHIP" | "TOP" | "HULL" | null
+ ptypeNm?: string | null
}
// 프로퍼티 정의
@@ -58,9 +62,6 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
// 읽지 않은 메시지 개수
const [unreadMessages, setUnreadMessages] = useState<Record<number, number>>({})
- // 견적 비교 다이얼로그 상태 관리
- const [comparisonDialogOpen, setComparisonDialogOpen] = useState(false)
-
// 테이블 선택 상태 관리
const [selectedRows, setSelectedRows] = useState<RfqDetailView[]>([])
const [isSendingRfq, setIsSendingRfq] = useState(false)
@@ -69,6 +70,16 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
// 벤더 삭제 확인 다이얼로그 상태 추가
const [deleteConfirmDialogOpen, setDeleteConfirmDialogOpen] = useState(false)
+ // 견적 히스토리 다이얼로그 상태 관리
+ const [historyDialogOpen, setHistoryDialogOpen] = useState(false)
+ const [selectedQuotationId, setSelectedQuotationId] = useState<number | null>(null)
+
+ // 견적서 첨부파일 sheet 상태 관리
+ const [quotationAttachmentsSheetOpen, setQuotationAttachmentsSheetOpen] = useState(false)
+ const [selectedQuotationInfo, setSelectedQuotationInfo] = useState<QuotationInfo | null>(null)
+ const [quotationAttachments, setQuotationAttachments] = useState<QuotationAttachment[]>([])
+ const [isLoadingAttachments, setIsLoadingAttachments] = useState(false)
+
// selectedRfq ID 메모이제이션 (객체 참조 변경 방지)
const selectedRfqId = useMemo(() => selectedRfq?.id, [selectedRfq?.id])
@@ -108,6 +119,8 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
detailId: item.id,
rfqId: selectedRfqId,
rfqCode: selectedRfq?.rfqCode || null,
+ rfqType: selectedRfq?.rfqType || null,
+ ptypeNm: selectedRfq?.ptypeNm || null,
vendorId: item.vendorId ? Number(item.vendorId) : undefined,
})) || []
@@ -121,7 +134,7 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
console.error("데이터 새로고침 오류:", error)
toast.error("데이터를 새로고침하는 중 오류가 발생했습니다")
}
- }, [selectedRfqId, selectedRfq?.rfqCode, loadUnreadMessages])
+ }, [selectedRfqId, selectedRfq?.rfqCode, selectedRfq?.rfqType, selectedRfq?.ptypeNm, loadUnreadMessages])
// 벤더 추가 핸들러 메모이제이션
const handleAddVendor = useCallback(async () => {
@@ -180,6 +193,54 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
}
}, [selectedRows, selectedRfqId, handleRefreshData]);
+ // 벤더 선택 핸들러 추가
+ const [isAcceptingVendors, setIsAcceptingVendors] = useState(false);
+
+ const handleAcceptVendors = useCallback(async () => {
+ if (selectedRows.length === 0) {
+ toast.warning("선택할 벤더를 선택해주세요.");
+ return;
+ }
+
+ if (selectedRows.length > 1) {
+ toast.warning("하나의 벤더만 선택할 수 있습니다.");
+ return;
+ }
+
+ const selectedQuotation = selectedRows[0];
+ if (selectedQuotation.status !== "Submitted") {
+ toast.warning("제출된 견적서만 선택할 수 있습니다.");
+ return;
+ }
+
+ try {
+ setIsAcceptingVendors(true);
+
+ // 벤더 견적 승인 서비스 함수 호출
+ const { acceptTechSalesVendorQuotationAction } = await import("@/lib/techsales-rfq/actions");
+
+ const result = await acceptTechSalesVendorQuotationAction(selectedQuotation.id);
+
+ if (result.success) {
+ toast.success(result.message || "벤더가 성공적으로 선택되었습니다.");
+ } else {
+ toast.error(result.error || "벤더 선택 중 오류가 발생했습니다.");
+ }
+
+ // 선택 해제
+ setSelectedRows([]);
+
+ // 데이터 새로고침
+ await handleRefreshData();
+
+ } catch (error) {
+ console.error("벤더 선택 오류:", error);
+ toast.error("벤더 선택 중 오류가 발생했습니다.");
+ } finally {
+ setIsAcceptingVendors(false);
+ }
+ }, [selectedRows, handleRefreshData]);
+
// 벤더 삭제 핸들러 메모이제이션
const handleDeleteVendors = useCallback(async () => {
if (selectedRows.length === 0) {
@@ -246,27 +307,47 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
await handleDeleteVendors();
}, [handleDeleteVendors]);
- // 견적 비교 다이얼로그 열기 핸들러 메모이제이션
- const handleOpenComparisonDialog = useCallback(() => {
- // 제출된 견적이 있는 벤더가 최소 1개 이상 있는지 확인
- const hasSubmittedQuotations = details.some(detail =>
- detail.status === "Submitted" // RfqDetailView의 실제 필드 사용
- );
- if (!hasSubmittedQuotations) {
- toast.warning("제출된 견적이 없습니다.");
- return;
- }
+ // 견적 히스토리 다이얼로그 열기 핸들러 메모이제이션
+ const handleOpenHistoryDialog = useCallback((quotationId: number) => {
+ setSelectedQuotationId(quotationId);
+ setHistoryDialogOpen(true);
+ }, [])
- setComparisonDialogOpen(true);
- }, [details])
+ // 견적서 첨부파일 sheet 열기 핸들러 메모이제이션
+ const handleOpenQuotationAttachmentsSheet = useCallback(async (quotationId: number, quotationInfo: QuotationInfo) => {
+ try {
+ setIsLoadingAttachments(true);
+ setSelectedQuotationInfo(quotationInfo);
+ setQuotationAttachmentsSheetOpen(true);
+
+ // 견적서 첨부파일 조회
+ const { getTechSalesVendorQuotationAttachments } = await import("@/lib/techsales-rfq/service");
+ const result = await getTechSalesVendorQuotationAttachments(quotationId);
+
+ if (result.error) {
+ toast.error(result.error);
+ setQuotationAttachments([]);
+ } else {
+ setQuotationAttachments(result.data || []);
+ }
+ } catch (error) {
+ console.error("견적서 첨부파일 조회 오류:", error);
+ toast.error("견적서 첨부파일을 불러오는 중 오류가 발생했습니다.");
+ setQuotationAttachments([]);
+ } finally {
+ setIsLoadingAttachments(false);
+ }
+ }, [])
// 칼럼 정의 - unreadMessages 상태 전달 (메모이제이션)
const columns = useMemo(() =>
getRfqDetailColumns({
setRowAction,
- unreadMessages
- }), [unreadMessages])
+ unreadMessages,
+ onQuotationClick: handleOpenHistoryDialog,
+ openQuotationAttachmentsSheet: handleOpenQuotationAttachmentsSheet
+ }), [unreadMessages, handleOpenHistoryDialog, handleOpenQuotationAttachmentsSheet])
// 필터 필드 정의 (메모이제이션)
const advancedFilterFields = useMemo(
@@ -493,6 +574,22 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
)}
</div>
<div className="flex gap-2">
+ {/* 벤더 선택 버튼 */}
+ <Button
+ variant="default"
+ size="sm"
+ onClick={handleAcceptVendors}
+ disabled={selectedRows.length === 0 || isAcceptingVendors}
+ className="gap-2"
+ >
+ {isAcceptingVendors ? (
+ <Loader2 className="size-4 animate-spin" aria-hidden="true" />
+ ) : (
+ <CheckCircle className="size-4" aria-hidden="true" />
+ )}
+ <span>벤더 선택</span>
+ </Button>
+
{/* RFQ 발송 버튼 */}
<Button
variant="outline"
@@ -525,22 +622,6 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
<span>벤더 삭제</span>
</Button>
- {/* 견적 비교 버튼 */}
- <Button
- variant="outline"
- size="sm"
- onClick={handleOpenComparisonDialog}
- className="gap-2"
- disabled={
- !selectedRfq ||
- details.length === 0 ||
- vendorsWithQuotations === 0
- }
- >
- <BarChart2 className="size-4" aria-hidden="true" />
- <span>견적 비교/선택</span>
- </Button>
-
{/* 벤더 추가 버튼 */}
<Button
variant="outline"
@@ -586,7 +667,7 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
<AddVendorDialog
open={vendorDialogOpen}
onOpenChange={setVendorDialogOpen}
- selectedRfq={selectedRfq}
+ selectedRfq={selectedRfq as unknown as TechSalesRfq}
existingVendorIds={existingVendorIds}
onSuccess={handleRefreshData}
/>
@@ -600,13 +681,6 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
onSuccess={handleRefreshData}
/>
- {/* 견적 비교 다이얼로그 */}
- <VendorQuotationComparisonDialog
- open={comparisonDialogOpen}
- onOpenChange={setComparisonDialogOpen}
- selectedRfq={selectedRfq}
- />
-
{/* 다중 벤더 삭제 확인 다이얼로그 */}
<DeleteVendorsDialog
open={deleteConfirmDialogOpen}
@@ -615,6 +689,22 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
onConfirm={executeDeleteVendors}
isLoading={isDeletingVendors}
/>
+
+ {/* 견적 히스토리 다이얼로그 */}
+ <QuotationHistoryDialog
+ open={historyDialogOpen}
+ onOpenChange={setHistoryDialogOpen}
+ quotationId={selectedQuotationId}
+ />
+
+ {/* 견적서 첨부파일 Sheet */}
+ <TechSalesQuotationAttachmentsSheet
+ open={quotationAttachmentsSheetOpen}
+ onOpenChange={setQuotationAttachmentsSheetOpen}
+ quotation={selectedQuotationInfo}
+ attachments={quotationAttachments}
+ isLoading={isLoadingAttachments}
+ />
</div>
)
} \ No newline at end of file