summaryrefslogtreecommitdiff
path: root/lib/bidding/detail/table/bidding-vendor-prices-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/detail/table/bidding-vendor-prices-dialog.tsx')
-rw-r--r--lib/bidding/detail/table/bidding-vendor-prices-dialog.tsx297
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>
- )
-}