diff options
Diffstat (limited to 'lib/bidding/detail/table/bidding-vendor-prices-dialog.tsx')
| -rw-r--r-- | lib/bidding/detail/table/bidding-vendor-prices-dialog.tsx | 297 |
1 files changed, 0 insertions, 297 deletions
diff --git a/lib/bidding/detail/table/bidding-vendor-prices-dialog.tsx b/lib/bidding/detail/table/bidding-vendor-prices-dialog.tsx deleted file mode 100644 index dfcef812..00000000 --- a/lib/bidding/detail/table/bidding-vendor-prices-dialog.tsx +++ /dev/null @@ -1,297 +0,0 @@ -'use client' - -import * as React from 'react' -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' -import { Badge } from '@/components/ui/badge' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '@/components/ui/table' -import { - DollarSign, - Building, - TrendingDown, - TrendingUp -} from 'lucide-react' -import { useToast } from '@/hooks/use-toast' -import { getVendorPricesForBidding } from '../service' - -interface VendorPrice { - companyId: number - companyName: string - biddingCompanyId: number - totalAmount: number - currency: string - itemPrices: Array<{ - prItemId: number - itemName: string - quantity: number - quantityUnit: string - unitPrice: number - amount: number - proposedDeliveryDate?: string - }> -} - -interface BiddingVendorPricesDialogProps { - open: boolean - onOpenChange: (open: boolean) => void - biddingId: number - biddingTitle: string - budget?: number | null - targetPrice?: number | null - currency?: string -} - -export function BiddingVendorPricesDialog({ - open, - onOpenChange, - biddingId, - biddingTitle, - budget, - targetPrice, - currency = 'KRW' -}: BiddingVendorPricesDialogProps) { - const { toast } = useToast() - const [vendorPrices, setVendorPrices] = React.useState<VendorPrice[]>([]) - const [isLoading, setIsLoading] = React.useState(false) - - const loadVendorPrices = React.useCallback(async () => { - setIsLoading(true) - try { - const data = await getVendorPricesForBidding(biddingId) - setVendorPrices(data) - } catch (error) { - console.error('Failed to load vendor prices:', error) - toast({ - title: '오류', - description: '입찰가 정보를 불러오는데 실패했습니다.', - variant: 'destructive', - }) - } finally { - setIsLoading(false) - } - }, [biddingId, toast]) - - // 다이얼로그가 열릴 때 데이터 로드 - React.useEffect(() => { - if (open) { - loadVendorPrices() - } - }, [open, loadVendorPrices]) - - - // 금액 포맷팅 - const formatCurrency = (amount: number) => { - return new Intl.NumberFormat('ko-KR', { - style: 'currency', - currency: currency, - minimumFractionDigits: 0, - maximumFractionDigits: 0, - }).format(amount) - } - - // 수량 포맷팅 - const formatQuantity = (quantity: number, unit: string) => { - return `${quantity.toLocaleString()} ${unit}` - } - - // 최저가 계산 - const getLowestPrice = (itemPrices: VendorPrice['itemPrices']) => { - const validPrices = itemPrices.filter(item => item.quantity > 0) - - if (validPrices.length === 0) return null - - const prices = validPrices.map(item => item.unitPrice) - return Math.min(...prices) - } - - // 최고가 계산 - const getHighestPrice = (itemPrices: VendorPrice['itemPrices']) => { - const validPrices = itemPrices.filter(item => item.quantity > 0) - - if (validPrices.length === 0) return null - - const prices = validPrices.map(item => item.unitPrice) - return Math.max(...prices) - } - - return ( - <Dialog open={open} onOpenChange={onOpenChange}> - <DialogContent className="max-w-7xl max-h-[90vh] overflow-y-auto"> - <DialogHeader> - <DialogTitle className="flex items-center gap-2"> - <DollarSign className="w-6 h-6" /> - <span>입찰가 비교 분석</span> - <Badge variant="outline" className="ml-auto"> - {biddingTitle} - </Badge> - </DialogTitle> - <DialogDescription> - 협력업체별 품목별 입찰가 정보를 비교하여 최적의 낙찰 대상을 선정할 수 있습니다. - </DialogDescription> - </DialogHeader> - - <div className="space-y-6"> - {/* 상단 요약 정보 */} - <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> - <Card> - <CardHeader className="pb-3"> - <CardTitle className="text-sm font-medium flex items-center gap-2"> - <DollarSign className="w-4 h-4" /> - 예산 - </CardTitle> - </CardHeader> - <CardContent> - <div className="text-2xl font-bold text-blue-600"> - {budget ? formatCurrency(budget) : '-'} - </div> - </CardContent> - </Card> - - <Card> - <CardHeader className="pb-3"> - <CardTitle className="text-sm font-medium flex items-center gap-2"> - <TrendingDown className="w-4 h-4" /> - 내정가 - </CardTitle> - </CardHeader> - <CardContent> - <div className="text-2xl font-bold text-green-600"> - {targetPrice ? formatCurrency(targetPrice) : '-'} - </div> - </CardContent> - </Card> - - <Card> - <CardHeader className="pb-3"> - <CardTitle className="text-sm font-medium flex items-center gap-2"> - <Building className="w-4 h-4" /> - 참여 업체 수 - </CardTitle> - </CardHeader> - <CardContent> - <div className="text-2xl font-bold text-purple-600"> - {vendorPrices.length}개사 - </div> - </CardContent> - </Card> - </div> - - - {isLoading ? ( - <div className="flex items-center justify-center py-12"> - <div className="text-center"> - <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div> - <p className="text-muted-foreground">입찰가 정보를 불러오는 중...</p> - </div> - </div> - ) : vendorPrices.length > 0 ? ( - <div className="space-y-4"> - {vendorPrices.map((vendor) => ( - <Card key={vendor.companyId}> - <CardHeader> - <CardTitle className="flex items-center justify-between"> - <div className="flex items-center gap-2"> - <Building className="w-5 h-5" /> - <span>{vendor.companyName}</span> - </div> - <div className="text-right"> - <div className="text-lg font-bold text-green-600"> - {formatCurrency(vendor.totalAmount)} - </div> - <div className="text-xs text-muted-foreground"> - 총 입찰금액 - </div> - </div> - </CardTitle> - </CardHeader> - <CardContent> - <Table> - <TableHeader> - <TableRow> - <TableHead>품목명</TableHead> - <TableHead className="text-right">수량</TableHead> - <TableHead className="text-right">단가</TableHead> - <TableHead className="text-right">금액</TableHead> - <TableHead className="text-center">가격대</TableHead> - <TableHead>납기일</TableHead> - </TableRow> - </TableHeader> - <TableBody> - {vendor.itemPrices - .filter(item => item.quantity > 0) - .map((item, index) => { - const lowestPrice = getLowestPrice(vendor.itemPrices) - const highestPrice = getHighestPrice(vendor.itemPrices) - const isLowest = item.unitPrice === lowestPrice - const isHighest = item.unitPrice === highestPrice - - return ( - <TableRow key={`${item.prItemId}-${index}`}> - <TableCell className="font-medium"> - {item.itemName} - </TableCell> - <TableCell className="text-right font-mono"> - {formatQuantity(item.quantity, item.quantityUnit)} - </TableCell> - <TableCell className="text-right font-mono"> - {formatCurrency(item.unitPrice)} - </TableCell> - <TableCell className="text-right font-mono"> - {formatCurrency(item.amount)} - </TableCell> - <TableCell className="text-center"> - <div className="flex justify-center"> - {isLowest && ( - <Badge variant="destructive" className="text-xs"> - <TrendingDown className="w-3 h-3 mr-1" /> - 최저 - </Badge> - )} - {isHighest && ( - <Badge variant="secondary" className="text-xs"> - <TrendingUp className="w-3 h-3 mr-1" /> - 최고 - </Badge> - )} - </div> - </TableCell> - <TableCell> - {item.proposedDeliveryDate ? - new Date(item.proposedDeliveryDate).toLocaleDateString('ko-KR') : - '-' - } - </TableCell> - </TableRow> - ) - })} - </TableBody> - </Table> - </CardContent> - </Card> - ))} - </div> - ) : ( - <div className="text-center py-12 text-gray-500"> - <DollarSign className="w-12 h-12 mx-auto mb-4 opacity-50" /> - <p className="text-lg font-medium mb-2">입찰가 정보가 없습니다</p> - <p className="text-sm">협력업체들이 아직 입찰가를 제출하지 않았습니다.</p> - </div> - )} - </div> - </DialogContent> - </Dialog> - ) -} |
