summaryrefslogtreecommitdiff
path: root/lib/bidding/selection/bidding-item-table.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/selection/bidding-item-table.tsx')
-rw-r--r--lib/bidding/selection/bidding-item-table.tsx192
1 files changed, 192 insertions, 0 deletions
diff --git a/lib/bidding/selection/bidding-item-table.tsx b/lib/bidding/selection/bidding-item-table.tsx
new file mode 100644
index 00000000..c101f7e7
--- /dev/null
+++ b/lib/bidding/selection/bidding-item-table.tsx
@@ -0,0 +1,192 @@
+'use client'
+
+import * as React from 'react'
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
+import {
+ getPRItemsForBidding,
+ getVendorPricesForBidding
+} from '@/lib/bidding/detail/service'
+import { formatNumber } from '@/lib/utils'
+import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
+
+interface BiddingItemTableProps {
+ biddingId: number
+}
+
+export function BiddingItemTable({ biddingId }: BiddingItemTableProps) {
+ const [data, setData] = React.useState<{
+ prItems: any[]
+ vendorPrices: any[]
+ }>({ prItems: [], vendorPrices: [] })
+ const [loading, setLoading] = React.useState(true)
+
+ React.useEffect(() => {
+ const loadData = async () => {
+ try {
+ setLoading(true)
+ const [prItems, vendorPrices] = await Promise.all([
+ getPRItemsForBidding(biddingId),
+ getVendorPricesForBidding(biddingId)
+ ])
+ console.log('prItems', prItems)
+ console.log('vendorPrices', vendorPrices)
+ setData({ prItems, vendorPrices })
+ } catch (error) {
+ console.error('Failed to load bidding items:', error)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ loadData()
+ }, [biddingId])
+
+ if (loading) {
+ return (
+ <Card>
+ <CardHeader>
+ <CardTitle>응찰품목</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="flex items-center justify-center py-8">
+ <div className="text-sm text-muted-foreground">로딩 중...</div>
+ </div>
+ </CardContent>
+ </Card>
+ )
+ }
+
+ const { prItems, vendorPrices } = data
+
+ // Calculate Totals
+ const totalQuantity = prItems.reduce((sum, item) => sum + Number(item.quantity || 0), 0)
+ const totalWeight = prItems.reduce((sum, item) => sum + Number(item.totalWeight || 0), 0)
+ const totalTargetAmount = prItems.reduce((sum, item) => sum + Number(item.targetAmount || 0), 0)
+
+ // Calculate Vendor Totals
+ const vendorTotals = vendorPrices.map(vendor => {
+ const total = vendor.itemPrices.reduce((sum: number, item: any) => sum + Number(item.amount || 0), 0)
+ return {
+ companyId: vendor.companyId,
+ totalAmount: total
+ }
+ })
+
+ return (
+ <Card>
+ <CardHeader>
+ <CardTitle>응찰품목</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <ScrollArea className="w-full whitespace-nowrap rounded-md border">
+ <div className="w-max min-w-full">
+ <table className="w-full caption-bottom text-sm">
+ <thead className="[&_tr]:border-b">
+ {/* Header Row 1: Base Info + Vendor Groups */}
+ <tr className="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
+ <th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground border-r" rowSpan={2}>자재번호</th>
+ <th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground border-r" rowSpan={2}>자재내역</th>
+ <th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground border-r" rowSpan={2}>자재내역상세</th>
+ <th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground border-r" rowSpan={2}>구매단위</th>
+ <th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground border-r" rowSpan={2}>수량</th>
+ <th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground border-r" rowSpan={2}>단위</th>
+ <th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground border-r" rowSpan={2}>총중량</th>
+ <th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground border-r" rowSpan={2}>중량단위</th>
+ <th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground border-r" rowSpan={2}>내정단가</th>
+ <th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground border-r" rowSpan={2}>내정액</th>
+ <th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground border-r" rowSpan={2}>통화</th>
+
+ {vendorPrices.map((vendor) => (
+ <th key={vendor.companyId} colSpan={4} className="h-12 px-4 text-center align-middle font-medium text-muted-foreground border-r bg-muted/20">
+ {vendor.companyName}
+ </th>
+ ))}
+ </tr>
+ {/* Header Row 2: Vendor Sub-columns */}
+ <tr className="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
+ {vendorPrices.map((vendor) => (
+ <React.Fragment key={vendor.companyId}>
+ <th className="h-10 px-2 text-center align-middle font-medium text-muted-foreground border-r bg-muted/10">단가</th>
+ <th className="h-10 px-2 text-center align-middle font-medium text-muted-foreground border-r bg-muted/10">총액</th>
+ <th className="h-10 px-2 text-center align-middle font-medium text-muted-foreground border-r bg-muted/10">통화</th>
+ <th className="h-10 px-2 text-center align-middle font-medium text-muted-foreground border-r bg-muted/10">내정액(%)</th>
+ </React.Fragment>
+ ))}
+ </tr>
+ </thead>
+ <tbody className="[&_tr:last-child]:border-0">
+ {/* Summary Row */}
+ <tr className="border-b transition-colors hover:bg-muted/50 bg-muted/30 font-semibold">
+ <td className="p-4 align-middle text-center border-r" colSpan={4}>합계</td>
+ <td className="p-4 align-middle text-right border-r">{formatNumber(totalQuantity)}</td>
+ <td className="p-4 align-middle text-center border-r">-</td>
+ <td className="p-4 align-middle text-right border-r">{formatNumber(totalWeight)}</td>
+ <td className="p-4 align-middle text-center border-r">-</td>
+ <td className="p-4 align-middle text-center border-r">-</td>
+ <td className="p-4 align-middle text-right border-r">{formatNumber(totalTargetAmount)}</td>
+ <td className="p-4 align-middle text-center border-r">KRW</td>
+
+ {vendorPrices.map((vendor) => {
+ const vTotal = vendorTotals.find(t => t.companyId === vendor.companyId)?.totalAmount || 0
+ const ratio = totalTargetAmount > 0 ? (vTotal / totalTargetAmount) * 100 : 0
+ return (
+ <React.Fragment key={vendor.companyId}>
+ <td className="p-4 align-middle text-center border-r">-</td>
+ <td className="p-4 align-middle text-right border-r">{formatNumber(vTotal)}</td>
+ <td className="p-4 align-middle text-center border-r">{vendor.currency}</td>
+ <td className="p-4 align-middle text-right border-r">{formatNumber(ratio, 0)}%</td>
+ </React.Fragment>
+ )
+ })}
+ </tr>
+
+ {/* Data Rows */}
+ {prItems.map((item) => (
+ <tr key={item.id} className="border-b transition-colors hover:bg-muted/50">
+ <td className="p-4 align-middle border-r">{item.materialNumber}</td>
+ <td className="p-4 align-middle border-r min-w-[150px]">{item.materialInfo}</td>
+ <td className="p-4 align-middle border-r min-w-[150px]">{item.specification}</td>
+ <td className="p-4 align-middle text-center border-r">{item.purchaseUnit}</td>
+ <td className="p-4 align-middle text-right border-r">{formatNumber(item.quantity)}</td>
+ <td className="p-4 align-middle text-center border-r">{item.quantityUnit}</td>
+ <td className="p-4 align-middle text-right border-r">{formatNumber(item.totalWeight)}</td>
+ <td className="p-4 align-middle text-center border-r">{item.weightUnit}</td>
+ <td className="p-4 align-middle text-right border-r">{formatNumber(item.targetUnitPrice)}</td>
+ <td className="p-4 align-middle text-right border-r">{formatNumber(item.targetAmount)}</td>
+ <td className="p-4 align-middle text-center border-r">{item.currency}</td>
+
+ {vendorPrices.map((vendor) => {
+ const bidItem = vendor.itemPrices.find((p: any) => p.prItemId === item.id)
+ const bidAmount = bidItem ? bidItem.amount : 0
+ const targetAmt = Number(item.targetAmount || 0)
+ const ratio = targetAmt > 0 && bidAmount > 0 ? (bidAmount / targetAmt) * 100 : 0
+
+ return (
+ <React.Fragment key={vendor.companyId}>
+ <td className="p-4 align-middle text-right border-r bg-muted/5">
+ {bidItem ? formatNumber(bidItem.unitPrice) : '-'}
+ </td>
+ <td className="p-4 align-middle text-right border-r bg-muted/5">
+ {bidItem ? formatNumber(bidItem.amount) : '-'}
+ </td>
+ <td className="p-4 align-middle text-center border-r bg-muted/5">
+ {vendor.currency}
+ </td>
+ <td className="p-4 align-middle text-right border-r bg-muted/5">
+ {bidItem && ratio > 0 ? `${formatNumber(ratio, 0)}%` : '-'}
+ </td>
+ </React.Fragment>
+ )
+ })}
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ <ScrollBar orientation="horizontal" />
+ </ScrollArea>
+ </CardContent>
+ </Card>
+ )
+}
+