summaryrefslogtreecommitdiff
path: root/lib/bidding/detail/table/bidding-detail-target-price-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/detail/table/bidding-detail-target-price-dialog.tsx')
-rw-r--r--lib/bidding/detail/table/bidding-detail-target-price-dialog.tsx356
1 files changed, 0 insertions, 356 deletions
diff --git a/lib/bidding/detail/table/bidding-detail-target-price-dialog.tsx b/lib/bidding/detail/table/bidding-detail-target-price-dialog.tsx
deleted file mode 100644
index a8f604d8..00000000
--- a/lib/bidding/detail/table/bidding-detail-target-price-dialog.tsx
+++ /dev/null
@@ -1,356 +0,0 @@
-'use client'
-
-import * as React from 'react'
-import { Bidding } from '@/db/schema'
-import { QuotationDetails, updateTargetPrice, calculateAndUpdateTargetPrice, getPreQuoteData } from '@/lib/bidding/detail/service'
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from '@/components/ui/dialog'
-import { Button } from '@/components/ui/button'
-import { Input } from '@/components/ui/input'
-import { Label } from '@/components/ui/label'
-import { Textarea } from '@/components/ui/textarea'
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from '@/components/ui/table'
-import { useToast } from '@/hooks/use-toast'
-import { useTransition } from 'react'
-
-interface BiddingDetailTargetPriceDialogProps {
- open: boolean
- onOpenChange: (open: boolean) => void
- quotationDetails: QuotationDetails | null
- bidding: Bidding
- onSuccess: () => void
-}
-
-export function BiddingDetailTargetPriceDialog({
- open,
- onOpenChange,
- quotationDetails,
- bidding,
- onSuccess
-}: BiddingDetailTargetPriceDialogProps) {
- const { toast } = useToast()
- const [isPending, startTransition] = useTransition()
- const [targetPrice, setTargetPrice] = React.useState(
- bidding.targetPrice ? Number(bidding.targetPrice) : 0
- )
- const [calculationCriteria, setCalculationCriteria] = React.useState(
- (bidding as any).targetPriceCalculationCriteria || ''
- )
- const [preQuoteData, setPreQuoteData] = React.useState<any>(null)
- const [isAutoCalculating, setIsAutoCalculating] = React.useState(false)
-
- // Dialog가 열릴 때 상태 초기화 및 사전견적 데이터 로드
- React.useEffect(() => {
- if (open) {
- setTargetPrice(bidding.targetPrice ? Number(bidding.targetPrice) : 0)
- setCalculationCriteria((bidding as any).targetPriceCalculationCriteria || '')
-
- // 사전견적 데이터 로드
- const loadPreQuoteData = async () => {
- try {
- const data = await getPreQuoteData(bidding.id)
- setPreQuoteData(data)
- } catch (error) {
- console.error('Failed to load pre-quote data:', error)
- }
- }
- loadPreQuoteData()
- }
- }, [open, bidding])
-
- // 자동 산정 함수
- const handleAutoCalculate = () => {
- setIsAutoCalculating(true)
-
- startTransition(async () => {
- try {
- const result = await calculateAndUpdateTargetPrice(
- bidding.id
- )
-
- if (result.success && result.data) {
- setTargetPrice(result.data.targetPrice)
- setCalculationCriteria(result.data.criteria)
- setPreQuoteData(result.data.preQuoteData)
-
- toast({
- title: '성공',
- description: result.message,
- })
-
- onSuccess()
- } else {
- toast({
- title: '오류',
- description: result.error,
- variant: 'destructive',
- })
- }
- } catch (error) {
- toast({
- title: '오류',
- description: '내정가 자동 산정에 실패했습니다.',
- variant: 'destructive',
- })
- } finally {
- setIsAutoCalculating(false)
- }
- })
- }
-
- const handleSave = () => {
- // 필수값 검증
- if (targetPrice <= 0) {
- toast({
- title: '유효성 오류',
- description: '내정가는 0보다 큰 값을 입력해주세요.',
- variant: 'destructive',
- })
- return
- }
-
- if (!calculationCriteria.trim()) {
- toast({
- title: '유효성 오류',
- description: '내정가 산정 기준을 입력해주세요.',
- variant: 'destructive',
- })
- return
- }
-
- startTransition(async () => {
- const result = await updateTargetPrice(
- bidding.id,
- targetPrice,
- calculationCriteria.trim()
- )
-
- if (result.success) {
- toast({
- title: '성공',
- description: result.message,
- })
- onSuccess()
- onOpenChange(false)
- } else {
- toast({
- title: '오류',
- description: result.error,
- variant: 'destructive',
- })
- }
- })
- }
-
- const formatCurrency = (amount: number) => {
- return new Intl.NumberFormat('ko-KR', {
- style: 'currency',
- currency: bidding.currency || 'KRW',
- }).format(amount)
- }
-
- return (
- <Dialog open={open} onOpenChange={onOpenChange}>
- <DialogContent className="sm:max-w-[800px]">
- <DialogHeader>
- <DialogTitle>내정가 산정</DialogTitle>
- <DialogDescription>
- 입찰번호: {bidding.biddingNumber} - 견적 통계 및 내정가 설정
- </DialogDescription>
- </DialogHeader>
-
- <div className="space-y-4">
- {/* 사전견적 리스트 */}
- {preQuoteData?.quotes && preQuoteData.quotes.length > 0 && (
- <div className="mb-4">
- <h4 className="text-sm font-medium mb-2">사전견적 현황</h4>
- <div className="border rounded-lg">
- <Table>
- <TableHeader>
- <TableRow>
- <TableHead>업체명</TableHead>
- <TableHead className="text-right">사전견적가</TableHead>
- <TableHead className="text-right">제출일</TableHead>
- </TableRow>
- </TableHeader>
- <TableBody>
- {preQuoteData.quotes.map((quote: any) => (
- <TableRow key={quote.id}>
- <TableCell className="font-medium">
- {quote.vendorName || `업체 ${quote.companyId}`}
- </TableCell>
- <TableCell className="text-right font-mono">
- {formatCurrency(Number(quote.preQuoteAmount))}
- </TableCell>
- <TableCell className="text-right text-sm text-muted-foreground">
- {quote.submittedAt
- ? new Date(quote.submittedAt).toLocaleDateString('ko-KR')
- : '-'
- }
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- </Table>
- </div>
- </div>
- )}
-
- <Table>
- <TableHeader>
- <TableRow>
- <TableHead className="w-[200px]">항목</TableHead>
- <TableHead>값</TableHead>
- </TableRow>
- </TableHeader>
- <TableBody>
- {/* 사전견적 통계 정보 */}
- <TableRow>
- <TableCell className="font-medium">사전견적 수</TableCell>
- <TableCell className="font-semibold">
- {preQuoteData?.quotationCount || 0}개
- </TableCell>
- </TableRow>
- {preQuoteData?.lowestQuote && (
- <TableRow>
- <TableCell className="font-medium">최저 사전견적가</TableCell>
- <TableCell className="font-semibold text-green-600">
- {formatCurrency(preQuoteData.lowestQuote)}
- </TableCell>
- </TableRow>
- )}
- {preQuoteData?.highestQuote && (
- <TableRow>
- <TableCell className="font-medium">최고 사전견적가</TableCell>
- <TableCell className="font-semibold text-blue-600">
- {formatCurrency(preQuoteData.highestQuote)}
- </TableCell>
- </TableRow>
- )}
- {preQuoteData?.averageQuote && (
- <TableRow>
- <TableCell className="font-medium">평균 사전견적가</TableCell>
- <TableCell className="font-semibold">
- {formatCurrency(preQuoteData.averageQuote)}
- </TableCell>
- </TableRow>
- )}
-
- {/* 입찰 유형 */}
- <TableRow>
- <TableCell className="font-medium">입찰 유형</TableCell>
- <TableCell className="font-semibold">
- {bidding.biddingType || '-'}
- </TableCell>
- </TableRow>
-
- {/* 예산 정보 */}
- {bidding.budget && (
- <TableRow>
- <TableCell className="font-medium">예산</TableCell>
- <TableCell className="font-semibold">
- {formatCurrency(Number(bidding.budget))}
- </TableCell>
- </TableRow>
- )}
-
- {/* 최종 업데이트 시간 */}
- {quotationDetails?.lastUpdated && (
- <TableRow>
- <TableCell className="font-medium">최종 업데이트</TableCell>
- <TableCell className="text-sm text-muted-foreground">
- {new Date(quotationDetails.lastUpdated).toLocaleString('ko-KR')}
- </TableCell>
- </TableRow>
- )}
-
- {/* 내정가 입력 */}
- <TableRow>
- <TableCell className="font-medium">
- <Label htmlFor="targetPrice" className="text-sm font-medium">
- 내정가 *
- </Label>
- </TableCell>
- <TableCell>
- <div className="space-y-2">
- <div className="flex gap-2">
- <Input
- id="targetPrice"
- type="number"
- value={targetPrice}
- onChange={(e) => setTargetPrice(Number(e.target.value))}
- placeholder="내정가를 입력하세요"
- className="flex-1"
- />
- <Button
- type="button"
- variant="outline"
- onClick={handleAutoCalculate}
- disabled={isAutoCalculating || isPending || !preQuoteData?.quotationCount}
- className="whitespace-nowrap"
- >
- {isAutoCalculating ? '산정 중...' : '자동 산정'}
- </Button>
- </div>
- <div className="text-sm text-muted-foreground">
- {targetPrice > 0 ? formatCurrency(targetPrice) : ''}
- </div>
- {preQuoteData?.quotationCount === 0 && (
- <div className="text-xs text-orange-600">
- 사전견적 데이터가 없어 자동 산정이 불가능합니다.
- </div>
- )}
- </div>
- </TableCell>
- </TableRow>
-
- {/* 내정가 산정 기준 입력 */}
- <TableRow>
- <TableCell className="font-medium align-top pt-2">
- <Label htmlFor="calculationCriteria" className="text-sm font-medium">
- 내정가 산정 기준 *
- </Label>
- </TableCell>
- <TableCell>
- <Textarea
- id="calculationCriteria"
- value={calculationCriteria}
- onChange={(e) => setCalculationCriteria(e.target.value)}
- placeholder="내정가 산정 기준을 자세히 입력해주세요. 자동 산정 시 입찰유형에 따른 기준이 자동 설정됩니다."
- className="w-full min-h-[100px]"
- rows={4}
- />
- <div className="text-xs text-muted-foreground mt-1">
- 필수 입력 사항입니다. 내정가 산정에 대한 근거를 명확히 기재해주세요.
- </div>
- </TableCell>
- </TableRow>
- </TableBody>
- </Table>
- </div>
-
- <DialogFooter>
- <Button variant="outline" onClick={() => onOpenChange(false)}>
- 취소
- </Button>
- <Button onClick={handleSave} disabled={isPending || isAutoCalculating}>
- 저장
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- )
-}