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