diff options
Diffstat (limited to 'lib/bidding/detail/table/bidding-detail-content.tsx')
| -rw-r--r-- | lib/bidding/detail/table/bidding-detail-content.tsx | 314 |
1 files changed, 0 insertions, 314 deletions
diff --git a/lib/bidding/detail/table/bidding-detail-content.tsx b/lib/bidding/detail/table/bidding-detail-content.tsx deleted file mode 100644 index 05c7d567..00000000 --- a/lib/bidding/detail/table/bidding-detail-content.tsx +++ /dev/null @@ -1,314 +0,0 @@ -'use client' - -import * as React from 'react' -import { Bidding } from '@/db/schema' -import { QuotationDetails, QuotationVendor } from '@/lib/bidding/detail/service' - -import { BiddingDetailVendorTableContent } from './bidding-detail-vendor-table' -import { BiddingDetailItemsDialog } from './bidding-detail-items-dialog' -import { BiddingDetailTargetPriceDialog } from './bidding-detail-target-price-dialog' -import { BiddingPreQuoteItemDetailsDialog } from '../../../bidding/pre-quote/table/bidding-pre-quote-item-details-dialog' -import { getPrItemsForBidding } from '../../../bidding/pre-quote/service' -import { checkAllVendorsFinalSubmitted, performBidOpening } from '../bidding-actions' -import { useToast } from '@/hooks/use-toast' -import { useTransition } from 'react' -import { useSession } from 'next-auth/react' -import { BiddingNoticeEditor } from '@/lib/bidding/bidding-notice-editor' -import { getBiddingNotice } from '@/lib/bidding/service' -import { Button } from '@/components/ui/button' -import { Card, CardContent } from '@/components/ui/card' -import { Badge } from '@/components/ui/badge' -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog' -import { FileText, Eye, CheckCircle2, AlertCircle } from 'lucide-react' - -interface BiddingDetailContentProps { - bidding: Bidding - quotationDetails: QuotationDetails | null - quotationVendors: QuotationVendor[] - prItems: any[] -} - -export function BiddingDetailContent({ - bidding, - quotationDetails, - quotationVendors, - prItems -}: BiddingDetailContentProps) { - const { toast } = useToast() - const [isPending, startTransition] = useTransition() - const session = useSession() - - const [dialogStates, setDialogStates] = React.useState({ - items: false, - targetPrice: false, - selectionReason: false, - award: false, - biddingNotice: false - }) - - const [, setRefreshTrigger] = React.useState(0) - - // PR 아이템 다이얼로그 관련 state - const [isItemDetailsDialogOpen, setIsItemDetailsDialogOpen] = React.useState(false) - const [selectedVendorForDetails, setSelectedVendorForDetails] = React.useState<QuotationVendor | null>(null) - const [prItemsForDialog, setPrItemsForDialog] = React.useState<any[]>([]) - - // 입찰공고 관련 state - const [biddingNotice, setBiddingNotice] = React.useState<any>(null) - const [isBiddingNoticeLoading, setIsBiddingNoticeLoading] = React.useState(false) - - // 최종제출 현황 관련 state - const [finalSubmissionStatus, setFinalSubmissionStatus] = React.useState<{ - allSubmitted: boolean - totalCompanies: number - submittedCompanies: number - }>({ allSubmitted: false, totalCompanies: 0, submittedCompanies: 0 }) - const [isPerformingBidOpening, setIsPerformingBidOpening] = React.useState(false) - - const handleRefresh = React.useCallback(() => { - setRefreshTrigger(prev => prev + 1) - }, []) - - // 입찰공고 로드 함수 - const loadBiddingNotice = React.useCallback(async () => { - if (!bidding.id) return - - setIsBiddingNoticeLoading(true) - try { - const notice = await getBiddingNotice(bidding.id) - setBiddingNotice(notice) - } catch (error) { - console.error('Failed to load bidding notice:', error) - toast({ - title: '오류', - description: '입찰공고문을 불러오는데 실패했습니다.', - variant: 'destructive', - }) - } finally { - setIsBiddingNoticeLoading(false) - } - }, [bidding.id, toast]) - - const openDialog = React.useCallback((type: keyof typeof dialogStates) => { - setDialogStates(prev => ({ ...prev, [type]: true })) - }, []) - - // 최종제출 현황 로드 함수 - const loadFinalSubmissionStatus = React.useCallback(async () => { - if (!bidding.id) return - - try { - const status = await checkAllVendorsFinalSubmitted(bidding.id) - setFinalSubmissionStatus(status) - } catch (error) { - console.error('Failed to load final submission status:', error) - } - }, [bidding.id]) - - // 개찰 핸들러 - const handlePerformBidOpening = async (isEarly: boolean = false) => { - if (!session.data?.user?.id) { - toast({ - title: '권한 없음', - description: '로그인이 필요합니다.', - variant: 'destructive', - }) - return - } - - if (!finalSubmissionStatus.allSubmitted) { - toast({ - title: '개찰 불가', - description: `모든 벤더가 최종 제출해야 개찰할 수 있습니다. (${finalSubmissionStatus.submittedCompanies}/${finalSubmissionStatus.totalCompanies})`, - variant: 'destructive', - }) - return - } - - const message = isEarly ? '조기개찰을 진행하시겠습니까?' : '개찰을 진행하시겠습니까?' - if (!window.confirm(message)) { - return - } - - setIsPerformingBidOpening(true) - try { - const result = await performBidOpening(bidding.id, session.data.user.id.toString(), isEarly) - - if (result.success) { - toast({ - title: '개찰 완료', - description: result.message, - }) - // 페이지 새로고침 - window.location.reload() - } else { - toast({ - title: '개찰 실패', - description: result.error, - variant: 'destructive', - }) - } - } catch (error) { - console.error('Failed to perform bid opening:', error) - toast({ - title: '오류', - description: '개찰에 실패했습니다.', - variant: 'destructive', - }) - } finally { - setIsPerformingBidOpening(false) - } - } - - // 컴포넌트 마운트 시 입찰공고 및 최종제출 현황 로드 - React.useEffect(() => { - loadBiddingNotice() - loadFinalSubmissionStatus() - }, [loadBiddingNotice, loadFinalSubmissionStatus]) - - const closeDialog = React.useCallback((type: keyof typeof dialogStates) => { - setDialogStates(prev => ({ ...prev, [type]: false })) - }, []) - - const handleViewItemDetails = React.useCallback((vendor: QuotationVendor) => { - startTransition(async () => { - try { - // PR 아이템 정보 로드 - const prItemsData = await getPrItemsForBidding(bidding.id) - setPrItemsForDialog(prItemsData) - setSelectedVendorForDetails(vendor) - setIsItemDetailsDialogOpen(true) - } catch (error) { - console.error('Failed to load PR items:', error) - toast({ - title: '오류', - description: '품목 정보를 불러오는데 실패했습니다.', - variant: 'destructive', - }) - } - }) - }, [bidding.id, toast]) - - // 개찰 버튼 표시 여부 (입찰평가중 상태에서만) - const showBidOpeningButtons = bidding.status === 'evaluation_of_bidding' - - return ( - <div className="space-y-6"> - {/* 입찰공고 편집 버튼 */} - <div className="flex justify-between items-center"> - <div> - <h2 className="text-2xl font-bold">입찰 상세</h2> - <p className="text-muted-foreground">{bidding.title}</p> - </div> - <Dialog open={dialogStates.biddingNotice} onOpenChange={(open) => setDialogStates(prev => ({ ...prev, biddingNotice: open }))}> - <DialogTrigger asChild> - <Button variant="outline" className="gap-2"> - <FileText className="h-4 w-4" /> - 입찰공고 편집 - </Button> - </DialogTrigger> - <DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden"> - <DialogHeader> - <DialogTitle>입찰공고 편집</DialogTitle> - </DialogHeader> - <div className="max-h-[60vh] overflow-y-auto"> - <BiddingNoticeEditor - initialData={biddingNotice} - biddingId={bidding.id} - onSaveSuccess={() => setDialogStates(prev => ({ ...prev, biddingNotice: false }))} - /> - </div> - </DialogContent> - </Dialog> - </div> - - {/* 최종제출 현황 및 개찰 버튼 */} - {showBidOpeningButtons && ( - <Card> - <CardContent className="pt-6"> - <div className="flex items-center justify-between"> - <div className="flex items-center gap-4"> - <div> - <div className="flex items-center gap-2 mb-1"> - {finalSubmissionStatus.allSubmitted ? ( - <CheckCircle2 className="h-5 w-5 text-green-600" /> - ) : ( - <AlertCircle className="h-5 w-5 text-yellow-600" /> - )} - <h3 className="text-lg font-semibold">최종제출 현황</h3> - </div> - <div className="flex items-center gap-2"> - <span className="text-sm text-muted-foreground"> - 최종 제출: {finalSubmissionStatus.submittedCompanies}/{finalSubmissionStatus.totalCompanies}개 업체 - </span> - {finalSubmissionStatus.allSubmitted ? ( - <Badge variant="default">모든 업체 제출 완료</Badge> - ) : ( - <Badge variant="secondary">제출 대기 중</Badge> - )} - </div> - </div> - </div> - - {/* 개찰 버튼들 */} - <div className="flex gap-2"> - <Button - onClick={() => handlePerformBidOpening(false)} - disabled={!finalSubmissionStatus.allSubmitted || isPerformingBidOpening} - variant="default" - > - <Eye className="h-4 w-4 mr-2" /> - {isPerformingBidOpening ? '처리 중...' : '개찰'} - </Button> - <Button - onClick={() => handlePerformBidOpening(true)} - disabled={!finalSubmissionStatus.allSubmitted || isPerformingBidOpening} - variant="outline" - > - <Eye className="h-4 w-4 mr-2" /> - {isPerformingBidOpening ? '처리 중...' : '조기개찰'} - </Button> - </div> - </div> - </CardContent> - </Card> - )} - - <BiddingDetailVendorTableContent - biddingId={bidding.id} - bidding={bidding} - vendors={quotationVendors} - onRefresh={handleRefresh} - onOpenTargetPriceDialog={() => openDialog('targetPrice')} - onOpenSelectionReasonDialog={() => openDialog('selectionReason')} - onViewItemDetails={handleViewItemDetails} - onEdit={undefined} - /> - - <BiddingDetailItemsDialog - open={dialogStates.items} - onOpenChange={(open) => closeDialog('items')} - prItems={prItems} - bidding={bidding} - /> - - <BiddingDetailTargetPriceDialog - open={dialogStates.targetPrice} - onOpenChange={(open) => closeDialog('targetPrice')} - quotationDetails={quotationDetails} - bidding={bidding} - onSuccess={handleRefresh} - /> - - <BiddingPreQuoteItemDetailsDialog - open={isItemDetailsDialogOpen} - onOpenChange={setIsItemDetailsDialogOpen} - biddingId={bidding.id} - biddingCompanyId={selectedVendorForDetails?.id || 0} - companyName={selectedVendorForDetails?.vendorName || ''} - prItems={prItemsForDialog} - currency={bidding.currency || 'KRW'} - /> - </div> - ) -} |
