summaryrefslogtreecommitdiff
path: root/lib/bidding/pre-quote
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/pre-quote')
-rw-r--r--lib/bidding/pre-quote/service.ts5
-rw-r--r--lib/bidding/pre-quote/table/bidding-pre-quote-item-details-dialog.tsx123
-rw-r--r--lib/bidding/pre-quote/table/bidding-pre-quote-vendor-columns.tsx51
-rw-r--r--lib/bidding/pre-quote/table/bidding-pre-quote-vendor-table.tsx68
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'}
+ />
</>
)
}