diff options
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.tsx | 341 |
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> - </> - ) -} |
