summaryrefslogtreecommitdiff
path: root/lib/techsales-rfq/table/detail-table/vendor-quotation-comparison-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/techsales-rfq/table/detail-table/vendor-quotation-comparison-dialog.tsx')
-rw-r--r--lib/techsales-rfq/table/detail-table/vendor-quotation-comparison-dialog.tsx341
1 files changed, 0 insertions, 341 deletions
diff --git a/lib/techsales-rfq/table/detail-table/vendor-quotation-comparison-dialog.tsx b/lib/techsales-rfq/table/detail-table/vendor-quotation-comparison-dialog.tsx
deleted file mode 100644
index 0a6caa5c..00000000
--- a/lib/techsales-rfq/table/detail-table/vendor-quotation-comparison-dialog.tsx
+++ /dev/null
@@ -1,341 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { useEffect, useState } from "react"
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog"
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
-import { Badge } from "@/components/ui/badge"
-import { Button } from "@/components/ui/button"
-import { Skeleton } from "@/components/ui/skeleton"
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@/components/ui/table"
-import { toast } from "sonner"
-import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"
-
-// Lucide 아이콘
-import { Plus, Minus, CheckCircle, Loader2 } from "lucide-react"
-
-import { getTechSalesVendorQuotationsWithJoin } from "@/lib/techsales-rfq/service"
-import { acceptTechSalesVendorQuotationAction } from "@/lib/techsales-rfq/actions"
-import { formatCurrency, formatDate } from "@/lib/utils"
-import { techSalesVendorQuotations } from "@/db/schema/techSales"
-
-// 기술영업 견적 정보 타입
-interface TechSalesVendorQuotation {
- id: number
- rfqId: number
- vendorId: number
- vendorName?: string | null
- totalPrice: string | null
- currency: string | null
- validUntil: Date | null
- status: string
- remark: string | null
- submittedAt: Date | null
- acceptedAt: Date | null
- createdAt: Date
- updatedAt: Date
-}
-
-interface VendorQuotationComparisonDialogProps {
- open: boolean
- onOpenChange: (open: boolean) => void
- selectedRfq: {
- id: number;
- rfqCode: string | null;
- status: string;
- [key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
- } | null
-}
-
-export function VendorQuotationComparisonDialog({
- open,
- onOpenChange,
- selectedRfq,
-}: VendorQuotationComparisonDialogProps) {
- const [isLoading, setIsLoading] = useState(false)
- const [quotations, setQuotations] = useState<TechSalesVendorQuotation[]>([])
- const [selectedVendorId, setSelectedVendorId] = useState<number | null>(null)
- const [isAccepting, setIsAccepting] = useState(false)
- const [showConfirmDialog, setShowConfirmDialog] = useState(false)
-
- useEffect(() => {
- async function loadQuotationData() {
- if (!open || !selectedRfq?.id) return
-
- try {
- setIsLoading(true)
- // 기술영업 견적 목록 조회 (제출된 견적만)
- const result = await getTechSalesVendorQuotationsWithJoin({
- rfqId: selectedRfq.id,
- page: 1,
- perPage: 100,
- filters: [
- {
- id: "status" as keyof typeof techSalesVendorQuotations,
- value: "Submitted",
- type: "select" as const,
- operator: "eq" as const,
- rowId: "status"
- }
- ]
- })
-
- setQuotations(result.data || [])
- } catch (error) {
- console.error("견적 데이터 로드 오류:", error)
- toast.error("견적 데이터를 불러오는 데 실패했습니다")
- } finally {
- setIsLoading(false)
- }
- }
-
- loadQuotationData()
- }, [open, selectedRfq])
-
- // 견적 상태 -> 뱃지 색
- const getStatusBadgeVariant = (status: string) => {
- switch (status) {
- case "Submitted":
- return "default"
- case "Accepted":
- return "default"
- case "Rejected":
- return "destructive"
- case "Revised":
- return "destructive"
- default:
- return "secondary"
- }
- }
-
- // 벤더 선택 핸들러
- const handleSelectVendor = (vendorId: number) => {
- setSelectedVendorId(vendorId)
- setShowConfirmDialog(true)
- }
-
- // 벤더 선택 확정
- const handleConfirmSelection = async () => {
- if (!selectedVendorId) return
-
- try {
- setIsAccepting(true)
-
- // 선택된 견적의 ID 찾기
- const selectedQuotation = quotations.find(q => q.vendorId === selectedVendorId)
- if (!selectedQuotation) {
- toast.error("선택된 견적을 찾을 수 없습니다")
- return
- }
-
- // 벤더 선택 API 호출
- const result = await acceptTechSalesVendorQuotationAction(selectedQuotation.id)
-
- if (result.success) {
- toast.success(result.message || "벤더가 선택되었습니다")
- setShowConfirmDialog(false)
- onOpenChange(false)
-
- // 페이지 새로고침 또는 데이터 재로드
- window.location.reload()
- } else {
- toast.error(result.error || "벤더 선택에 실패했습니다")
- }
- } catch (error) {
- console.error("벤더 선택 오류:", error)
- toast.error("벤더 선택에 실패했습니다")
- } finally {
- setIsAccepting(false)
- }
- }
-
- const selectedVendor = quotations.find(q => q.vendorId === selectedVendorId)
-
- return (
- <>
- <Dialog open={open} onOpenChange={onOpenChange}>
- <DialogContent className="max-w-[90vw] lg:max-w-5xl max-h-[90vh]">
- <DialogHeader>
- <DialogTitle>벤더 견적 비교 및 선택</DialogTitle>
- <DialogDescription>
- {selectedRfq
- ? `RFQ ${selectedRfq.rfqCode} - 제출된 견적을 비교하고 벤더를 선택하세요`
- : ""}
- </DialogDescription>
- </DialogHeader>
-
- {isLoading ? (
- <div className="space-y-4">
- <Skeleton className="h-8 w-1/2" />
- <Skeleton className="h-48 w-full" />
- </div>
- ) : quotations.length === 0 ? (
- <div className="py-8 text-center text-muted-foreground">
- 제출된(Submitted) 견적이 없습니다
- </div>
- ) : (
- <div className="border rounded-md max-h-[60vh] overflow-auto">
- <table className="table-fixed w-full border-collapse">
- <thead className="sticky top-0 bg-background z-10">
- <TableRow>
- <TableHead className="sticky left-0 top-0 z-20 bg-background p-2 w-32">
- 항목
- </TableHead>
- {quotations.map((q) => (
- <TableHead key={q.id} className="p-2 text-center whitespace-nowrap w-48">
- <div className="flex flex-col items-center gap-2">
- <span>{q.vendorName || `벤더 ID: ${q.vendorId}`}</span>
- <Button
- size="sm"
- variant={q.status === "Accepted" ? "default" : "outline"}
- onClick={() => handleSelectVendor(q.vendorId)}
- disabled={q.status === "Accepted"}
- className="gap-1"
- >
- {q.status === "Accepted" ? (
- <>
- <CheckCircle className="h-4 w-4" />
- 선택됨
- </>
- ) : (
- "선택"
- )}
- </Button>
- </div>
- </TableHead>
- ))}
- </TableRow>
- </thead>
- <tbody>
- {/* 견적 상태 */}
- <TableRow>
- <TableCell className="sticky left-0 z-10 bg-muted/30 p-2 font-medium">
- 견적 상태
- </TableCell>
- {quotations.map((q) => (
- <TableCell key={`status-${q.id}`} className="p-2 text-center">
- <Badge variant={getStatusBadgeVariant(q.status)}>
- {q.status}
- </Badge>
- </TableCell>
- ))}
- </TableRow>
-
- {/* 총 금액 */}
- <TableRow>
- <TableCell className="sticky left-0 z-10 bg-muted/30 p-2 font-medium">
- 총 금액
- </TableCell>
- {quotations.map((q) => (
- <TableCell key={`total-${q.id}`} className="p-2 font-semibold text-center">
- {q.totalPrice ? formatCurrency(Number(q.totalPrice), q.currency || 'USD') : '-'}
- </TableCell>
- ))}
- </TableRow>
-
- {/* 통화 */}
- <TableRow>
- <TableCell className="sticky left-0 z-10 bg-muted/30 p-2 font-medium">
- 통화
- </TableCell>
- {quotations.map((q) => (
- <TableCell key={`currency-${q.id}`} className="p-2 text-center">
- {q.currency || '-'}
- </TableCell>
- ))}
- </TableRow>
-
- {/* 유효기간 */}
- <TableRow>
- <TableCell className="sticky left-0 z-10 bg-muted/30 p-2 font-medium">
- 유효 기간
- </TableCell>
- {quotations.map((q) => (
- <TableCell key={`valid-${q.id}`} className="p-2 text-center">
- {q.validUntil ? formatDate(q.validUntil, "KR") : '-'}
- </TableCell>
- ))}
- </TableRow>
-
- {/* 제출일 */}
- <TableRow>
- <TableCell className="sticky left-0 z-10 bg-muted/30 p-2 font-medium">
- 제출일
- </TableCell>
- {quotations.map((q) => (
- <TableCell key={`submitted-${q.id}`} className="p-2 text-center">
- {q.submittedAt ? formatDate(q.submittedAt, "KR") : '-'}
- </TableCell>
- ))}
- </TableRow>
-
- {/* 비고 */}
- <TableRow>
- <TableCell className="sticky left-0 z-10 bg-muted/30 p-2 font-medium">
- 비고
- </TableCell>
- {quotations.map((q) => (
- <TableCell
- key={`remark-${q.id}`}
- className="p-2 whitespace-pre-wrap text-center"
- >
- {q.remark || "-"}
- </TableCell>
- ))}
- </TableRow>
- </tbody>
- </table>
- </div>
- )}
-
- <DialogFooter>
- <Button variant="outline" onClick={() => onOpenChange(false)}>
- 닫기
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
-
- {/* 벤더 선택 확인 다이얼로그 */}
- <AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
- <AlertDialogContent>
- <AlertDialogHeader>
- <AlertDialogTitle>벤더 선택 확인</AlertDialogTitle>
- <AlertDialogDescription>
- <strong>{selectedVendor?.vendorName || `벤더 ID: ${selectedVendorId}`}</strong>를 선택하시겠습니까?
- <br />
- <br />
- 선택된 벤더의 견적이 승인되며, 다른 벤더들의 견적은 자동으로 거절됩니다.
- 이 작업은 되돌릴 수 없습니다.
- </AlertDialogDescription>
- </AlertDialogHeader>
- <AlertDialogFooter>
- <AlertDialogCancel disabled={isAccepting}>취소</AlertDialogCancel>
- <AlertDialogAction
- onClick={handleConfirmSelection}
- disabled={isAccepting}
- className="gap-2"
- >
- {isAccepting && <Loader2 className="h-4 w-4 animate-spin" />}
- 확인
- </AlertDialogAction>
- </AlertDialogFooter>
- </AlertDialogContent>
- </AlertDialog>
- </>
- )
-}