summaryrefslogtreecommitdiff
path: root/components/bidding/manage/bidding-items-editor.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/bidding/manage/bidding-items-editor.tsx')
-rw-r--r--components/bidding/manage/bidding-items-editor.tsx271
1 files changed, 240 insertions, 31 deletions
diff --git a/components/bidding/manage/bidding-items-editor.tsx b/components/bidding/manage/bidding-items-editor.tsx
index f0287ae4..208cf040 100644
--- a/components/bidding/manage/bidding-items-editor.tsx
+++ b/components/bidding/manage/bidding-items-editor.tsx
@@ -441,8 +441,8 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
materialGroupInfo: null,
materialNumber: null,
materialInfo: null,
- priceUnit: null,
- purchaseUnit: '1',
+ priceUnit: 1,
+ purchaseUnit: 'EA',
materialWeight: null,
wbsCode: null,
wbsName: null,
@@ -495,8 +495,8 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
prev.map((item) => {
if (item.id === id) {
const updatedItem = { ...item, ...updates }
- // 내정단가, 수량, 중량, 구매단위가 변경되면 내정금액 재계산
- if (updates.targetUnitPrice || updates.quantity || updates.totalWeight || updates.purchaseUnit) {
+ // 내정단가, 수량, 중량, 가격단위가 변경되면 내정금액 재계산
+ if (updates.targetUnitPrice || updates.quantity || updates.totalWeight || updates.priceUnit) {
updatedItem.targetAmount = calculateTargetAmount(updatedItem)
}
return updatedItem
@@ -533,20 +533,66 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
const calculateTargetAmount = (item: PRItemInfo): string => {
const unitPrice = parseFloat(item.targetUnitPrice?.replace(/,/g, '') || '0') || 0
- const purchaseUnit = parseFloat(item.purchaseUnit || '1') || 1
+ const priceUnit = parseFloat(item.priceUnit || '1') || 1
let amount = 0
if (quantityWeightMode === 'quantity') {
const quantity = parseFloat(item.quantity || '0') || 0
- amount = (quantity / purchaseUnit) * unitPrice
+ amount = (quantity / priceUnit) * unitPrice
} else {
const weight = parseFloat(item.totalWeight || '0') || 0
- amount = (weight / purchaseUnit) * unitPrice
+ amount = (weight / priceUnit) * unitPrice
}
return Math.floor(amount).toString()
}
+ // 합계 계산 함수들
+ const calculateTotals = () => {
+ let quantityTotal = 0
+ let weightTotal = 0
+ let targetAmountTotal = 0
+ let budgetAmountTotal = 0
+ let actualAmountTotal = 0
+
+ items.forEach((item) => {
+ // 수량 합계
+ if (item.quantity) {
+ quantityTotal += parseFloat(item.quantity) || 0
+ }
+
+ // 중량 합계
+ if (item.totalWeight) {
+ weightTotal += parseFloat(item.totalWeight) || 0
+ }
+
+ // 내정금액 합계
+ if (item.targetAmount) {
+ targetAmountTotal += parseFloat(item.targetAmount.replace(/,/g, '')) || 0
+ }
+
+ // 예산금액 합계
+ if (item.budgetAmount) {
+ budgetAmountTotal += parseFloat(item.budgetAmount.replace(/,/g, '')) || 0
+ }
+
+ // 실적금액 합계
+ if (item.actualAmount) {
+ actualAmountTotal += parseFloat(item.actualAmount.replace(/,/g, '')) || 0
+ }
+ })
+
+ return {
+ quantityTotal,
+ weightTotal,
+ targetAmountTotal,
+ budgetAmountTotal,
+ actualAmountTotal,
+ }
+ }
+
+ const totals = calculateTotals()
+
if (isLoading) {
return (
<div className="flex items-center justify-center p-8">
@@ -572,14 +618,16 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[120px]">프로젝트코드</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[300px]">프로젝트명</th>
- <th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[120px]">PR 번호</th>
+
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[150px]">자재그룹코드 <span className="text-red-500">*</span></th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[300px]">자재그룹명 <span className="text-red-500">*</span></th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[150px]">자재코드</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[300px]">자재명</th>
- <th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[120px]">수량</th>
- <th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[80px]">단위</th>
+ <th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[120px]">수량(중량) <span className="text-red-500">*</span></th>
+ <th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[80px]">단위 <span className="text-red-500">*</span></th>
+ <th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[80px]">가격단위</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[80px]">구매단위</th>
+ <th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[100px]">자재순중량</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[120px]">내정단가</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[120px]">내정금액</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[80px]">내정통화</th>
@@ -587,19 +635,120 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[80px]">예산통화</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[120px]">실적금액</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[80px]">실적통화</th>
+ <th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[120px]">납품요청일 <span className="text-red-500">*</span></th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[300px]">WBS코드</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[150px]">WBS명</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[120px]">코스트센터코드</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[150px]">코스트센터명</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[120px]">GL계정코드</th>
<th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[150px]">GL계정명</th>
- <th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[120px]">납품요청일 <span className="text-red-500">*</span></th>
+ <th className="border-r px-3 py-3 text-left text-xs font-medium min-w-[120px]">PR 번호</th>
<th className="sticky right-0 z-10 bg-muted/50 border-l px-3 py-3 text-center text-xs font-medium min-w-[100px]">
액션
</th>
</tr>
</thead>
<tbody>
+ {/* 합계 행 */}
+ <tr className="bg-blue-50 border-y-2 border-blue-200 font-semibold">
+ <td className="sticky left-0 z-10 bg-blue-50 border-r px-2 py-3 text-center">
+ <span className="text-xs">합계</span>
+ </td>
+ <td className="sticky left-[50px] z-10 bg-blue-50 border-r px-3 py-3 text-center">
+ <span className="text-xs">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs">
+ {quantityWeightMode === 'quantity'
+ ? `${formatNumberWithCommas(totals.quantityTotal.toString())}`
+ : `${formatNumberWithCommas(totals.weightTotal.toString())}`
+ }
+ </span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs">
+ {quantityWeightMode === 'quantity'
+ ? `${items[0]?.quantityUnit || 'EA'}`
+ : `${items[0]?.weightUnit || 'KG'}`
+ }
+ </span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs">{formatNumberWithCommas(totals.targetAmountTotal.toString())}</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs">{formatNumberWithCommas(totals.budgetAmountTotal.toString())}</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs">{formatNumberWithCommas(totals.actualAmountTotal.toString())}</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="border-r px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ <td className="sticky right-0 z-10 bg-blue-50 border-l px-3 py-3 text-center">
+ <span className="text-xs text-muted-foreground">-</span>
+ </td>
+ </tr>
{items.map((item, index) => (
<tr key={item.id} className="border-t hover:bg-muted/30">
<td className="sticky left-0 z-10 bg-background border-r px-2 py-2 text-center">
@@ -617,10 +766,17 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
<ProjectSelector
selectedProjectId={item.projectId || null}
onProjectSelect={(project) => {
- updatePRItem(item.id, {
- projectId: project.id,
- projectInfo: project.projectName
- })
+ if (project) {
+ updatePRItem(item.id, {
+ projectId: project.id,
+ projectInfo: project.projectName
+ })
+ } else {
+ updatePRItem(item.id, {
+ projectId: null,
+ projectInfo: null
+ })
+ }
}}
placeholder="프로젝트 선택"
/>
@@ -633,14 +789,7 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
className="h-8 text-xs bg-muted/50"
/>
</td>
- <td className="border-r px-3 py-2">
- <Input
- placeholder="PR 번호"
- value={item.prNumber || ''}
- readOnly
- className="h-8 text-xs bg-muted/50"
- />
- </td>
+
<td className="border-r px-3 py-2">
{biddingType !== 'equipment' ? (
<ProcurementItemSelectorDialogSingle
@@ -745,6 +894,7 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
value={item.quantity || ''}
onChange={(e) => updatePRItem(item.id, { quantity: e.target.value })}
className="h-8 text-xs"
+ required
/>
) : (
<Input
@@ -754,6 +904,7 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
value={item.totalWeight || ''}
onChange={(e) => updatePRItem(item.id, { totalWeight: e.target.value })}
className="h-8 text-xs"
+ required
/>
)}
</td>
@@ -762,6 +913,7 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
<Select
value={item.quantityUnit || 'EA'}
onValueChange={(value) => updatePRItem(item.id, { quantityUnit: value })}
+ required
>
<SelectTrigger className="h-8 text-xs">
<SelectValue />
@@ -779,6 +931,7 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
<Select
value={item.weightUnit || 'KG'}
onValueChange={(value) => updatePRItem(item.id, { weightUnit: value })}
+ required
>
<SelectTrigger className="h-8 text-xs">
<SelectValue />
@@ -797,9 +950,42 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
type="number"
min="1"
step="1"
- placeholder="구매단위"
- value={item.purchaseUnit || ''}
- onChange={(e) => updatePRItem(item.id, { purchaseUnit: e.target.value })}
+ placeholder="가격단위"
+ value={item.priceUnit || ''}
+ onChange={(e) => updatePRItem(item.id, { priceUnit: e.target.value })}
+ className="h-8 text-xs"
+ />
+ </td>
+ <td className="border-r px-3 py-2">
+ <Select
+ value={item.purchaseUnit || 'EA'}
+ onValueChange={(value) => updatePRItem(item.id, { purchaseUnit: value })}
+ >
+ <SelectTrigger className="h-8 text-xs">
+ <SelectValue />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectItem value="EA">EA</SelectItem>
+ <SelectItem value="SET">SET</SelectItem>
+ <SelectItem value="LOT">LOT</SelectItem>
+ <SelectItem value="M">M</SelectItem>
+ <SelectItem value="M2">M²</SelectItem>
+ <SelectItem value="M3">M³</SelectItem>
+ <SelectItem value="KG">KG</SelectItem>
+ <SelectItem value="TON">TON</SelectItem>
+ <SelectItem value="G">G</SelectItem>
+ <SelectItem value="LB">LB</SelectItem>
+ </SelectContent>
+ </Select>
+ </td>
+ <td className="border-r px-3 py-2">
+ <Input
+ type="number"
+ min="0"
+ step="0.01"
+ placeholder="자재순중량"
+ value={item.materialWeight || ''}
+ onChange={(e) => updatePRItem(item.id, { materialWeight: e.target.value })}
className="h-8 text-xs"
/>
</td>
@@ -888,9 +1074,23 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
</Select>
</td>
<td className="border-r px-3 py-2">
+ <Input
+ type="date"
+ value={item.requestedDeliveryDate || ''}
+ onChange={(e) => updatePRItem(item.id, { requestedDeliveryDate: e.target.value })}
+ className="h-8 text-xs"
+ required
+ />
+ </td>
+ <td className="border-r px-3 py-2">
<Button
variant="outline"
onClick={() => {
+ // 재클릭 시 기존 데이터 클리어
+ updatePRItem(item.id, {
+ wbsCode: null,
+ wbsName: null
+ })
setSelectedItemForWbs(item.id)
setWbsCodeDialogOpen(true)
}}
@@ -941,6 +1141,11 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
<Button
variant="outline"
onClick={() => {
+ // 재클릭 시 기존 데이터 클리어
+ updatePRItem(item.id, {
+ costCenterCode: null,
+ costCenterName: null
+ })
setSelectedItemForCostCenter(item.id)
setCostCenterDialogOpen(true)
}}
@@ -992,6 +1197,11 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
<Button
variant="outline"
onClick={() => {
+ // 재클릭 시 기존 데이터 클리어
+ updatePRItem(item.id, {
+ glAccountCode: null,
+ glAccountName: null
+ })
setSelectedItemForGlAccount(item.id)
setGlAccountDialogOpen(true)
}}
@@ -1039,11 +1249,10 @@ export function BiddingItemsEditor({ biddingId, readonly = false }: BiddingItems
</td>
<td className="border-r px-3 py-2">
<Input
- type="date"
- value={item.requestedDeliveryDate || ''}
- onChange={(e) => updatePRItem(item.id, { requestedDeliveryDate: e.target.value })}
- className="h-8 text-xs"
- required
+ placeholder="PR 번호"
+ value={item.prNumber || ''}
+ readOnly
+ className="h-8 text-xs bg-muted/50"
/>
</td>
<td className="sticky right-0 z-10 bg-background border-l px-3 py-2">