diff options
Diffstat (limited to 'lib/bidding/pre-quote')
4 files changed, 233 insertions, 14 deletions
diff --git a/lib/bidding/pre-quote/service.ts b/lib/bidding/pre-quote/service.ts index bf7a4538..c34f6f9e 100644 --- a/lib/bidding/pre-quote/service.ts +++ b/lib/bidding/pre-quote/service.ts @@ -663,6 +663,8 @@ export async function getPrItemsForBidding(biddingId: number) { // SPEC 문서 조회 (PR 아이템에 연결된 문서들) export async function getSpecDocumentsForPrItem(prItemId: number) { try { + console.log('getSpecDocumentsForPrItem called with prItemId:', prItemId) + const specDocs = await db .select({ id: biddingDocuments.id, @@ -678,10 +680,11 @@ export async function getSpecDocumentsForPrItem(prItemId: number) { .where( and( eq(biddingDocuments.prItemId, prItemId), - eq(biddingDocuments.documentType, 'specification') + eq(biddingDocuments.documentType, 'spec_document') ) ) + console.log('getSpecDocumentsForPrItem result:', specDocs) return specDocs } catch (error) { console.error('Failed to get spec documents for PR item:', error) diff --git a/lib/bidding/pre-quote/table/bidding-pre-quote-item-details-dialog.tsx b/lib/bidding/pre-quote/table/bidding-pre-quote-item-details-dialog.tsx new file mode 100644 index 00000000..12bd2696 --- /dev/null +++ b/lib/bidding/pre-quote/table/bidding-pre-quote-item-details-dialog.tsx @@ -0,0 +1,123 @@ +'use client' + +import * as React from 'react' +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { PrItemsPricingTable } from '../../vendor/components/pr-items-pricing-table' +import { getSavedPrItemQuotations } from '../service' + +interface PrItem { + id: number + itemNumber: string | null + prNumber: string | null + itemInfo: string | null + materialDescription: string | null + quantity: string | null + quantityUnit: string | null + currency: string | null + requestedDeliveryDate: string | null + hasSpecDocument: boolean | null +} + +interface PrItemQuotation { + prItemId: number + bidUnitPrice: number + bidAmount: number + proposedDeliveryDate?: string + technicalSpecification?: string +} + +interface BiddingPreQuoteItemDetailsDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + biddingId: number + biddingCompanyId: number + companyName: string + prItems: PrItem[] + currency?: string +} + +export function BiddingPreQuoteItemDetailsDialog({ + open, + onOpenChange, + biddingId, + biddingCompanyId, + companyName, + prItems, + currency = 'KRW' +}: BiddingPreQuoteItemDetailsDialogProps) { + const [prItemQuotations, setPrItemQuotations] = React.useState<PrItemQuotation[]>([]) + const [isLoading, setIsLoading] = React.useState(false) + + // 다이얼로그가 열릴 때 저장된 품목별 견적 데이터 로드 + React.useEffect(() => { + if (open && biddingCompanyId) { + loadSavedQuotations() + } + }, [open, biddingCompanyId]) + + const loadSavedQuotations = async () => { + setIsLoading(true) + try { + console.log('Loading saved quotations for biddingCompanyId:', biddingCompanyId) + const savedQuotations = await getSavedPrItemQuotations(biddingCompanyId) + console.log('Loaded saved quotations:', savedQuotations) + setPrItemQuotations(savedQuotations) + } catch (error) { + console.error('Failed to load saved quotations:', error) + } finally { + setIsLoading(false) + } + } + + const handleQuotationsChange = (quotations: PrItemQuotation[]) => { + // ReadOnly 모드이므로 변경사항을 저장하지 않음 + console.log('Quotations changed (readonly):', quotations) + } + + const handleTotalAmountChange = (total: number) => { + // ReadOnly 모드이므로 총 금액 변경을 처리하지 않음 + console.log('Total amount changed (readonly):', total) + } + + 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"> + <span>품목별 견적 상세</span> + <span className="text-sm font-normal text-muted-foreground"> + - {companyName} + </span> + </DialogTitle> + <DialogDescription> + 협력업체가 제출한 품목별 견적 상세 정보입니다. + </DialogDescription> + </DialogHeader> + + {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> + ) : ( + <PrItemsPricingTable + prItems={prItems} + initialQuotations={prItemQuotations} + currency={currency} + onQuotationsChange={handleQuotationsChange} + onTotalAmountChange={handleTotalAmountChange} + readOnly={true} + /> + )} + </DialogContent> + </Dialog> + ) +} diff --git a/lib/bidding/pre-quote/table/bidding-pre-quote-vendor-columns.tsx b/lib/bidding/pre-quote/table/bidding-pre-quote-vendor-columns.tsx index 30cddbce..39fcb30f 100644 --- a/lib/bidding/pre-quote/table/bidding-pre-quote-vendor-columns.tsx +++ b/lib/bidding/pre-quote/table/bidding-pre-quote-vendor-columns.tsx @@ -57,12 +57,16 @@ interface GetBiddingCompanyColumnsProps { onEdit: (company: BiddingCompany) => void onDelete: (company: BiddingCompany) => void onInvite: (company: BiddingCompany) => void + onViewPriceAdjustment?: (company: BiddingCompany) => void + onViewItemDetails?: (company: BiddingCompany) => void } export function getBiddingPreQuoteVendorColumns({ onEdit, onDelete, - onInvite + onInvite, + onViewPriceAdjustment, + onViewItemDetails }: GetBiddingCompanyColumnsProps): ColumnDef<BiddingCompany>[] { return [ { @@ -115,11 +119,24 @@ export function getBiddingPreQuoteVendorColumns({ { accessorKey: 'preQuoteAmount', header: '사전견적금액', - cell: ({ row }) => ( - <div className="text-right font-mono"> - {row.original.preQuoteAmount ? Number(row.original.preQuoteAmount).toLocaleString() : '-'} KRW - </div> - ), + cell: ({ row }) => { + const hasAmount = row.original.preQuoteAmount && Number(row.original.preQuoteAmount) > 0 + return ( + <div className="text-right font-mono"> + {hasAmount ? ( + <button + onClick={() => onViewItemDetails?.(row.original)} + className="text-primary hover:text-primary/80 hover:underline cursor-pointer" + title="품목별 견적 상세 보기" + > + {Number(row.original.preQuoteAmount).toLocaleString()} KRW + </button> + ) : ( + <span className="text-muted-foreground">-</span> + )} + </div> + ) + }, }, { accessorKey: 'preQuoteSubmittedAt', @@ -199,9 +216,21 @@ export function getBiddingPreQuoteVendorColumns({ const hasPriceAdjustment = row.original.priceAdjustmentResponse if (hasPriceAdjustment === null) return <div className="text-sm">-</div> return ( - <Badge variant={hasPriceAdjustment ? 'default' : 'secondary'}> - {hasPriceAdjustment ? '적용' : '미적용'} - </Badge> + <div className="flex items-center gap-2"> + <Badge variant={hasPriceAdjustment ? 'default' : 'secondary'}> + {hasPriceAdjustment ? '적용' : '미적용'} + </Badge> + {hasPriceAdjustment && onViewPriceAdjustment && ( + <Button + variant="ghost" + size="sm" + onClick={() => onViewPriceAdjustment(row.original)} + className="h-6 px-2 text-xs" + > + 상세 + </Button> + )} + </div> ) }, }, @@ -276,10 +305,10 @@ export function getBiddingPreQuoteVendorColumns({ </DropdownMenuTrigger> <DropdownMenuContent align="end"> <DropdownMenuLabel>작업</DropdownMenuLabel> - <DropdownMenuItem onClick={() => onEdit(company)}> + {/* <DropdownMenuItem onClick={() => onEdit(company)}> <Edit className="mr-2 h-4 w-4" /> 수정 - </DropdownMenuItem> + </DropdownMenuItem> */} {company.invitationStatus === 'pending' && ( <DropdownMenuItem onClick={() => onInvite(company)}> <UserPlus className="mr-2 h-4 w-4" /> diff --git a/lib/bidding/pre-quote/table/bidding-pre-quote-vendor-table.tsx b/lib/bidding/pre-quote/table/bidding-pre-quote-vendor-table.tsx index a9d12629..346bf9a6 100644 --- a/lib/bidding/pre-quote/table/bidding-pre-quote-vendor-table.tsx +++ b/lib/bidding/pre-quote/table/bidding-pre-quote-vendor-table.tsx @@ -12,8 +12,12 @@ import { Bidding } from '@/db/schema' import { deleteBiddingCompany } from '../service' +import { getPriceAdjustmentFormByBiddingCompanyId } from '@/lib/bidding/detail/service' import { useToast } from '@/hooks/use-toast' import { useTransition } from 'react' +import { PriceAdjustmentDialog } from '@/components/bidding/price-adjustment-dialog' +import { BiddingPreQuoteItemDetailsDialog } from './bidding-pre-quote-item-details-dialog' +import { getPrItemsForBidding } from '../service' interface BiddingPreQuoteVendorTableContentProps { biddingId: number @@ -97,6 +101,11 @@ export function BiddingPreQuoteVendorTableContent({ const [isPending, startTransition] = useTransition() const [selectedCompany, setSelectedCompany] = React.useState<BiddingCompany | null>(null) const [isEditDialogOpen, setIsEditDialogOpen] = React.useState(false) + const [isPriceAdjustmentDialogOpen, setIsPriceAdjustmentDialogOpen] = React.useState(false) + const [priceAdjustmentData, setPriceAdjustmentData] = React.useState<any>(null) + const [isItemDetailsDialogOpen, setIsItemDetailsDialogOpen] = React.useState(false) + const [selectedCompanyForDetails, setSelectedCompanyForDetails] = React.useState<BiddingCompany | null>(null) + const [prItems, setPrItems] = React.useState<any[]>([]) const handleDelete = (company: BiddingCompany) => { if (!confirm(`${company.companyName} 업체를 삭제하시겠습니까?`)) return @@ -133,13 +142,51 @@ export function BiddingPreQuoteVendorTableContent({ }) } + const handleViewPriceAdjustment = async (company: BiddingCompany) => { + startTransition(async () => { + const priceAdjustmentForm = await getPriceAdjustmentFormByBiddingCompanyId(company.id) + if (priceAdjustmentForm) { + setPriceAdjustmentData(priceAdjustmentForm) + setSelectedCompany(company) + setIsPriceAdjustmentDialogOpen(true) + } else { + toast({ + title: '정보 없음', + description: '연동제 정보가 없습니다.', + variant: 'destructive', + }) + } + }) + } + + const handleViewItemDetails = async (company: BiddingCompany) => { + startTransition(async () => { + try { + // PR 아이템 정보 로드 + const prItemsData = await getPrItemsForBidding(biddingId) + setPrItems(prItemsData) + setSelectedCompanyForDetails(company) + setIsItemDetailsDialogOpen(true) + } catch (error) { + console.error('Failed to load PR items:', error) + toast({ + title: '오류', + description: '품목 정보를 불러오는데 실패했습니다.', + variant: 'destructive', + }) + } + }) + } + const columns = React.useMemo( () => getBiddingPreQuoteVendorColumns({ onEdit: onEdit || handleEdit, onDelete: onDelete || handleDelete, - onInvite: handleInvite + onInvite: handleInvite, + onViewPriceAdjustment: handleViewPriceAdjustment, + onViewItemDetails: handleViewItemDetails }), - [onEdit, onDelete, handleEdit, handleDelete, handleInvite] + [onEdit, onDelete, handleEdit, handleDelete, handleInvite, handleViewPriceAdjustment, handleViewItemDetails] ) const { table } = useDataTable({ @@ -184,6 +231,23 @@ export function BiddingPreQuoteVendorTableContent({ onOpenChange={setIsEditDialogOpen} onSuccess={onRefresh} /> + + <PriceAdjustmentDialog + open={isPriceAdjustmentDialogOpen} + onOpenChange={setIsPriceAdjustmentDialogOpen} + data={priceAdjustmentData} + vendorName={selectedCompany?.companyName || ''} + /> + + <BiddingPreQuoteItemDetailsDialog + open={isItemDetailsDialogOpen} + onOpenChange={setIsItemDetailsDialogOpen} + biddingId={biddingId} + biddingCompanyId={selectedCompanyForDetails?.id || 0} + companyName={selectedCompanyForDetails?.companyName || ''} + prItems={prItems} + currency={bidding.currency || 'KRW'} + /> </> ) } |
