From ba8cd44a0ed2c613a5f2cee06bfc9bd0f61f21c7 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Fri, 7 Nov 2025 08:39:04 +0000 Subject: (최겸) 입찰/견적 수정사항 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/bidding/manage/bidding-items-editor.tsx | 1143 ++++++++++++++++++++ 1 file changed, 1143 insertions(+) create mode 100644 components/bidding/manage/bidding-items-editor.tsx (limited to 'components/bidding/manage/bidding-items-editor.tsx') diff --git a/components/bidding/manage/bidding-items-editor.tsx b/components/bidding/manage/bidding-items-editor.tsx new file mode 100644 index 00000000..96a8d2ae --- /dev/null +++ b/components/bidding/manage/bidding-items-editor.tsx @@ -0,0 +1,1143 @@ +'use client' + +import * as React from 'react' +import { Package, Plus, Trash2, Save, RefreshCw, FileText } from 'lucide-react' +import { getPRItemsForBidding } from '@/lib/bidding/detail/service' +import { updatePrItem } from '@/lib/bidding/detail/service' +import { toast } from 'sonner' +import { useSession } from 'next-auth/react' + +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { Checkbox } from '@/components/ui/checkbox' +import { ProjectSelector } from '@/components/ProjectSelector' +import { MaterialGroupSelectorDialogSingle } from '@/components/common/material/material-group-selector-dialog-single' +import { MaterialSelectorDialogSingle } from '@/components/common/selectors/material/material-selector-dialog-single' +import { WbsCodeSingleSelector } from '@/components/common/selectors/wbs-code/wbs-code-single-selector' +import { CostCenterSingleSelector } from '@/components/common/selectors/cost-center/cost-center-single-selector' +import { GlAccountSingleSelector } from '@/components/common/selectors/gl-account/gl-account-single-selector' + +// PR 아이템 정보 타입 (create-bidding-dialog와 동일) +interface PRItemInfo { + id: number // 실제 DB ID + prNumber?: string | null + projectId?: number | null + projectInfo?: string | null + shi?: string | null + quantity?: string | null + quantityUnit?: string | null + totalWeight?: string | null + weightUnit?: string | null + materialDescription?: string | null + hasSpecDocument?: boolean + requestedDeliveryDate?: string | null + isRepresentative?: boolean // 대표 아이템 여부 + // 가격 정보 + annualUnitPrice?: string | null + currency?: string | null + // 자재 그룹 정보 (필수) + materialGroupNumber?: string | null + materialGroupInfo?: string | null + // 자재 정보 + materialNumber?: string | null + materialInfo?: string | null + // 단위 정보 + priceUnit?: string | null + purchaseUnit?: string | null + materialWeight?: string | null + // WBS 정보 + wbsCode?: string | null + wbsName?: string | null + // Cost Center 정보 + costCenterCode?: string | null + costCenterName?: string | null + // GL Account 정보 + glAccountCode?: string | null + glAccountName?: string | null + // 내정 정보 + targetUnitPrice?: string | null + targetAmount?: string | null + targetCurrency?: string | null + // 예산 정보 + budgetAmount?: string | null + budgetCurrency?: string | null + // 실적 정보 + actualAmount?: string | null + actualCurrency?: string | null +} + +interface BiddingItemsEditorProps { + biddingId: number +} + +import { removeBiddingItem, addPRItemForBidding, getBiddingById, getBiddingConditions } from '@/lib/bidding/service' +import { CreatePreQuoteRfqDialog } from './create-pre-quote-rfq-dialog' +import { Textarea } from '@/components/ui/textarea' +import { Label } from '@/components/ui/label' + +export function BiddingItemsEditor({ biddingId }: BiddingItemsEditorProps) { + const { data: session } = useSession() + const [items, setItems] = React.useState([]) + const [isLoading, setIsLoading] = React.useState(false) + const [isSubmitting, setIsSubmitting] = React.useState(false) + const [quantityWeightMode, setQuantityWeightMode] = React.useState<'quantity' | 'weight'>('quantity') + const [costCenterDialogOpen, setCostCenterDialogOpen] = React.useState(false) + const [selectedItemForCostCenter, setSelectedItemForCostCenter] = React.useState(null) + const [glAccountDialogOpen, setGlAccountDialogOpen] = React.useState(false) + const [selectedItemForGlAccount, setSelectedItemForGlAccount] = React.useState(null) + const [wbsCodeDialogOpen, setWbsCodeDialogOpen] = React.useState(false) + const [selectedItemForWbs, setSelectedItemForWbs] = React.useState(null) + const [tempIdCounter, setTempIdCounter] = React.useState(0) // 임시 ID 카운터 + const [deletedItemIds, setDeletedItemIds] = React.useState>(new Set()) // 삭제된 아이템 ID 추적 + const [preQuoteDialogOpen, setPreQuoteDialogOpen] = React.useState(false) + const [targetPriceCalculationCriteria, setTargetPriceCalculationCriteria] = React.useState('') + const [biddingPicUserId, setBiddingPicUserId] = React.useState(null) + const [biddingConditions, setBiddingConditions] = React.useState<{ + paymentTerms?: string | null + taxConditions?: string | null + incoterms?: string | null + incotermsOption?: string | null + contractDeliveryDate?: string | null + shippingPort?: string | null + destinationPort?: string | null + isPriceAdjustmentApplicable?: boolean | null + sparePartOptions?: string | null + } | null>(null) + + // 초기 데이터 로딩 - 기존 품목이 있으면 자동으로 로드 + React.useEffect(() => { + const loadItems = async () => { + if (!biddingId) return + + setIsLoading(true) + try { + const prItems = await getPRItemsForBidding(biddingId) + + if (prItems && prItems.length > 0) { + const formattedItems: PRItemInfo[] = prItems.map((item) => ({ + id: item.id, + prNumber: item.prNumber || null, + projectId: item.projectId || null, + projectInfo: item.projectInfo || null, + shi: item.shi || null, + quantity: item.quantity ? item.quantity.toString() : null, + quantityUnit: item.quantityUnit || null, + totalWeight: item.totalWeight ? item.totalWeight.toString() : null, + weightUnit: item.weightUnit || null, + materialDescription: item.itemInfo || null, + hasSpecDocument: item.hasSpecDocument || false, + requestedDeliveryDate: item.requestedDeliveryDate ? new Date(item.requestedDeliveryDate).toISOString().split('T')[0] : null, + isRepresentative: false, // 첫 번째 아이템을 대표로 설정할 수 있음 + annualUnitPrice: item.annualUnitPrice ? item.annualUnitPrice.toString() : null, + currency: item.currency || 'KRW', + materialGroupNumber: item.materialGroupNumber || null, + materialGroupInfo: item.materialGroupInfo || null, + materialNumber: item.materialNumber || null, + materialInfo: item.materialInfo || null, + priceUnit: item.priceUnit || null, + purchaseUnit: item.purchaseUnit || null, + materialWeight: item.materialWeight ? item.materialWeight.toString() : null, + wbsCode: item.wbsCode || null, + wbsName: item.wbsName || null, + costCenterCode: item.costCenterCode || null, + costCenterName: item.costCenterName || null, + glAccountCode: item.glAccountCode || null, + glAccountName: item.glAccountName || null, + targetUnitPrice: item.targetUnitPrice ? item.targetUnitPrice.toString() : null, + targetAmount: item.targetAmount ? item.targetAmount.toString() : null, + targetCurrency: item.targetCurrency || 'KRW', + budgetAmount: item.budgetAmount ? item.budgetAmount.toString() : null, + budgetCurrency: item.budgetCurrency || 'KRW', + actualAmount: item.actualAmount ? item.actualAmount.toString() : null, + actualCurrency: item.actualCurrency || 'KRW', + })) + + // 첫 번째 아이템을 대표로 설정 + formattedItems[0].isRepresentative = true + + setItems(formattedItems) + setDeletedItemIds(new Set()) // 삭제 목록 초기화 + + // 기존 품목 로드 성공 알림 (조용히 표시, 선택적) + console.log(`기존 품목 ${formattedItems.length}개를 불러왔습니다.`) + } else { + // 품목이 없을 때는 빈 배열로 초기화 + setItems([]) + setDeletedItemIds(new Set()) + } + } catch (error) { + console.error('Failed to load items:', error) + toast.error('품목 정보를 불러오는데 실패했습니다.') + // 에러 발생 시에도 빈 배열로 초기화하여 UI가 깨지지 않도록 + setItems([]) + setDeletedItemIds(new Set()) + } finally { + setIsLoading(false) + } + } + + loadItems() + }, [biddingId]) + + // 입찰 정보 및 조건 로드 (사전견적 다이얼로그용) + React.useEffect(() => { + const loadBiddingInfo = async () => { + if (!biddingId) return + + try { + const [bidding, conditions] = await Promise.all([ + getBiddingById(biddingId), + getBiddingConditions(biddingId) + ]) + + if (bidding) { + setBiddingPicUserId(bidding.bidPicId || null) + setTargetPriceCalculationCriteria(bidding.targetPriceCalculationCriteria || '') + } + + if (conditions) { + setBiddingConditions(conditions) + } + } catch (error) { + console.error('Failed to load bidding info:', error) + } + } + + loadBiddingInfo() + }, [biddingId]) + + const handleSave = async () => { + setIsSubmitting(true) + try { + const userId = session?.user?.id?.toString() || '1' + let hasError = false + + // 모든 아이템을 upsert 처리 (id가 있으면 update, 없으면 insert) + for (const item of items) { + const targetAmount = calculateTargetAmount(item) + + let result + if (item.id > 0) { + // 기존 아이템 업데이트 + result = await updatePrItem(item.id, { + projectId: item.projectId || null, + projectInfo: item.projectInfo || null, + shi: item.shi || null, + materialGroupNumber: item.materialGroupNumber || null, + materialGroupInfo: item.materialGroupInfo || null, + materialNumber: item.materialNumber || null, + materialInfo: item.materialInfo || null, + quantity: item.quantity ? parseFloat(item.quantity) : null, + quantityUnit: item.quantityUnit || null, + totalWeight: item.totalWeight ? parseFloat(item.totalWeight) : null, + weightUnit: item.weightUnit || null, + priceUnit: item.priceUnit || null, + purchaseUnit: item.purchaseUnit || null, + materialWeight: item.materialWeight ? parseFloat(item.materialWeight) : null, + wbsCode: item.wbsCode || null, + wbsName: item.wbsName || null, + costCenterCode: item.costCenterCode || null, + costCenterName: item.costCenterName || null, + glAccountCode: item.glAccountCode || null, + glAccountName: item.glAccountName || null, + targetUnitPrice: item.targetUnitPrice ? parseFloat(item.targetUnitPrice) : null, + targetAmount: targetAmount ? parseFloat(targetAmount) : null, + targetCurrency: item.targetCurrency || 'KRW', + budgetAmount: item.budgetAmount ? parseFloat(item.budgetAmount) : null, + budgetCurrency: item.budgetCurrency || 'KRW', + actualAmount: item.actualAmount ? parseFloat(item.actualAmount) : null, + actualCurrency: item.actualCurrency || 'KRW', + requestedDeliveryDate: item.requestedDeliveryDate ? new Date(item.requestedDeliveryDate) : null, + currency: item.currency || 'KRW', + annualUnitPrice: item.annualUnitPrice ? parseFloat(item.annualUnitPrice) : null, + prNumber: item.prNumber || null, + hasSpecDocument: item.hasSpecDocument || false, + } as Parameters[1], userId) + } else { + // 새 아이템 추가 (문자열 타입만 허용) + result = await addPRItemForBidding(biddingId, { + projectId: item.projectId ?? undefined, + projectInfo: item.projectInfo ?? null, + shi: item.shi ?? null, + materialGroupNumber: item.materialGroupNumber ?? null, + materialGroupInfo: item.materialGroupInfo ?? null, + materialNumber: item.materialNumber ?? null, + materialInfo: item.materialInfo ?? null, + quantity: item.quantity ?? null, + quantityUnit: item.quantityUnit ?? null, + totalWeight: item.totalWeight ?? null, + weightUnit: item.weightUnit ?? null, + priceUnit: item.priceUnit ?? null, + purchaseUnit: item.purchaseUnit ?? null, + materialWeight: item.materialWeight ?? null, + wbsCode: item.wbsCode ?? null, + wbsName: item.wbsName ?? null, + costCenterCode: item.costCenterCode ?? null, + costCenterName: item.costCenterName ?? null, + glAccountCode: item.glAccountCode ?? null, + glAccountName: item.glAccountName ?? null, + targetUnitPrice: item.targetUnitPrice ?? null, + targetAmount: targetAmount ?? null, + targetCurrency: item.targetCurrency || 'KRW', + budgetAmount: item.budgetAmount ?? null, + budgetCurrency: item.budgetCurrency || 'KRW', + actualAmount: item.actualAmount ?? null, + actualCurrency: item.actualCurrency || 'KRW', + requestedDeliveryDate: item.requestedDeliveryDate ?? null, + currency: item.currency || 'KRW', + annualUnitPrice: item.annualUnitPrice ?? null, + prNumber: item.prNumber ?? null, + hasSpecDocument: item.hasSpecDocument || false, + }) + } + + if (!result.success) { + hasError = true + } + } + + // 삭제된 아이템들 서버에서 삭제 + for (const deletedId of deletedItemIds) { + const result = await removeBiddingItem(deletedId) + if (!result.success) { + hasError = true + } + } + + + if (hasError) { + toast.error('일부 품목 정보 저장에 실패했습니다.') + } else { + // 내정가 산정 기준 별도 저장 (서버 액션으로 처리) + if (targetPriceCalculationCriteria.trim()) { + try { + const { updateTargetPriceCalculationCriteria } = await import('@/lib/bidding/service') + const criteriaResult = await updateTargetPriceCalculationCriteria(biddingId, targetPriceCalculationCriteria.trim(), userId) + if (!criteriaResult.success) { + console.warn('Failed to save target price calculation criteria:', criteriaResult.error) + } + } catch (error) { + console.error('Failed to save target price calculation criteria:', error) + } + } + + toast.success('품목 정보가 성공적으로 저장되었습니다.') + // 삭제 목록 초기화 + setDeletedItemIds(new Set()) + // 데이터 다시 로딩하여 최신 상태 반영 + const prItems = await getPRItemsForBidding(biddingId) + const formattedItems: PRItemInfo[] = prItems.map((item) => ({ + id: item.id, + prNumber: item.prNumber || null, + projectId: item.projectId || null, + projectInfo: item.projectInfo || null, + shi: item.shi || null, + quantity: item.quantity ? item.quantity.toString() : null, + quantityUnit: item.quantityUnit || null, + totalWeight: item.totalWeight ? item.totalWeight.toString() : null, + weightUnit: item.weightUnit || null, + materialDescription: item.itemInfo || null, + hasSpecDocument: item.hasSpecDocument || false, + requestedDeliveryDate: item.requestedDeliveryDate ? new Date(item.requestedDeliveryDate).toISOString().split('T')[0] : null, + isRepresentative: false, + annualUnitPrice: item.annualUnitPrice ? item.annualUnitPrice.toString() : null, + currency: item.currency || 'KRW', + materialGroupNumber: item.materialGroupNumber || null, + materialGroupInfo: item.materialGroupInfo || null, + materialNumber: item.materialNumber || null, + materialInfo: item.materialInfo || null, + priceUnit: item.priceUnit || null, + purchaseUnit: item.purchaseUnit || null, + materialWeight: item.materialWeight ? item.materialWeight.toString() : null, + wbsCode: item.wbsCode || null, + wbsName: item.wbsName || null, + costCenterCode: item.costCenterCode || null, + costCenterName: item.costCenterName || null, + glAccountCode: item.glAccountCode || null, + glAccountName: item.glAccountName || null, + targetUnitPrice: item.targetUnitPrice ? item.targetUnitPrice.toString() : null, + targetAmount: item.targetAmount ? item.targetAmount.toString() : null, + targetCurrency: item.targetCurrency || 'KRW', + budgetAmount: item.budgetAmount ? item.budgetAmount.toString() : null, + budgetCurrency: item.budgetCurrency || 'KRW', + actualAmount: item.actualAmount ? item.actualAmount.toString() : null, + actualCurrency: item.actualCurrency || 'KRW', + })) + + // 첫 번째 아이템을 대표로 설정 + if (formattedItems.length > 0) { + formattedItems[0].isRepresentative = true + } + + setItems(formattedItems) + } + } catch (error) { + console.error('Failed to save items:', error) + toast.error('품목 정보 저장에 실패했습니다.') + } finally { + setIsSubmitting(false) + } + } + + const handleAddItem = () => { + // 임시 ID 생성 (음수로 구분하여 실제 DB ID와 구분) + const tempId = -(tempIdCounter + 1) + setTempIdCounter(prev => prev + 1) + + // 즉시 UI에 새 아이템 추가 (서버 저장 없음) + const newItem: PRItemInfo = { + id: tempId, // 임시 ID + prNumber: null, + projectId: null, + projectInfo: null, + shi: null, + quantity: null, + quantityUnit: 'EA', + totalWeight: null, + weightUnit: 'KG', + materialDescription: null, + hasSpecDocument: false, + requestedDeliveryDate: null, + isRepresentative: items.length === 0, + annualUnitPrice: null, + currency: 'KRW', + materialGroupNumber: null, + materialGroupInfo: null, + materialNumber: null, + materialInfo: null, + priceUnit: null, + purchaseUnit: '1', + materialWeight: null, + wbsCode: null, + wbsName: null, + costCenterCode: null, + costCenterName: null, + glAccountCode: null, + glAccountName: null, + targetUnitPrice: null, + targetAmount: null, + targetCurrency: 'KRW', + budgetAmount: null, + budgetCurrency: 'KRW', + actualAmount: null, + actualCurrency: 'KRW', + } + + setItems((prev) => { + // 첫 번째 아이템이면 대표로 설정 + if (prev.length === 0) { + return [newItem] + } + return [...prev, newItem] + }) + } + + const handleRemoveItem = (itemId: number) => { + if (items.length <= 1) { + toast.error('최소 하나의 품목이 필요합니다.') + return + } + + // 실제 아이템인 경우 삭제 목록에 추가 (저장 시 서버에서 삭제됨) + if (itemId > 0) { + setDeletedItemIds(prev => new Set([...prev, itemId])) + } + + // UI에서 즉시 제거 + setItems((prev) => { + const filteredItems = prev.filter((item) => item.id !== itemId) + const removedItem = prev.find((item) => item.id === itemId) + if (removedItem?.isRepresentative && filteredItems.length > 0) { + filteredItems[0].isRepresentative = true + } + return filteredItems + }) + } + + const updatePRItem = (id: number, updates: Partial) => { + setItems((prev) => + prev.map((item) => { + if (item.id === id) { + const updatedItem = { ...item, ...updates } + // 내정단가, 수량, 중량, 구매단위가 변경되면 내정금액 재계산 + if (updates.targetUnitPrice || updates.quantity || updates.totalWeight || updates.purchaseUnit) { + updatedItem.targetAmount = calculateTargetAmount(updatedItem) + } + return updatedItem + } + return item + }) + ) + } + + const setRepresentativeItem = (id: number) => { + setItems((prev) => + prev.map((item) => ({ + ...item, + isRepresentative: item.id === id, + })) + ) + } + + const handleQuantityWeightModeChange = (mode: 'quantity' | 'weight') => { + setQuantityWeightMode(mode) + } + + const calculateTargetAmount = (item: PRItemInfo) => { + const unitPrice = parseFloat(item.targetUnitPrice || '0') || 0 + const purchaseUnit = parseFloat(item.purchaseUnit || '1') || 1 + let amount = 0 + + if (quantityWeightMode === 'quantity') { + const quantity = parseFloat(item.quantity || '0') || 0 + amount = (quantity / purchaseUnit) * unitPrice + } else { + const weight = parseFloat(item.totalWeight || '0') || 0 + amount = (weight / purchaseUnit) * unitPrice + } + + return Math.floor(amount).toString() + } + + if (isLoading) { + return ( +
+
+ 품목 정보를 불러오는 중... +
+ ) + } + + // PR 아이템 테이블 렌더링 (create-bidding-dialog와 동일한 구조) + const renderPrItemsTable = () => { + return ( +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {items.map((item, index) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ))} + +
+ 대표 + + # + 프로젝트코드프로젝트명자재그룹코드 *자재그룹명 *자재코드자재명수량단위구매단위내정단가내정금액내정통화예산금액예산통화실적금액실적통화WBS코드WBS명코스트센터코드코스트센터명GL계정코드GL계정명납품요청일 + 액션 +
+ setRepresentativeItem(item.id)} + disabled={items.length <= 1 && item.isRepresentative} + title="대표 아이템" + /> + + {index + 1} + + { + updatePRItem(item.id, { + projectId: project.id, + projectInfo: project.projectName + }) + }} + placeholder="프로젝트 선택" + /> + + + + { + if (material) { + updatePRItem(item.id, { + materialGroupNumber: material.materialGroupCode, + materialGroupInfo: material.materialGroupDescription + }) + } else { + updatePRItem(item.id, { + materialGroupNumber: '', + materialGroupInfo: '' + }) + } + }} + title="자재그룹 선택" + description="자재그룹을 검색하고 선택해주세요." + /> + + + + { + if (material) { + updatePRItem(item.id, { + materialNumber: material.materialCode, + materialInfo: material.materialName + }) + } else { + updatePRItem(item.id, { + materialNumber: '', + materialInfo: '' + }) + } + }} + title="자재 선택" + description="자재를 검색하고 선택해주세요." + /> + + + + {quantityWeightMode === 'quantity' ? ( + updatePRItem(item.id, { quantity: e.target.value })} + className="h-8 text-xs" + /> + ) : ( + updatePRItem(item.id, { totalWeight: e.target.value })} + className="h-8 text-xs" + /> + )} + + {quantityWeightMode === 'quantity' ? ( + + ) : ( + + )} + + updatePRItem(item.id, { purchaseUnit: e.target.value })} + className="h-8 text-xs" + /> + + updatePRItem(item.id, { targetUnitPrice: e.target.value })} + className="h-8 text-xs" + /> + + + + + + updatePRItem(item.id, { budgetAmount: e.target.value })} + className="h-8 text-xs" + /> + + + + updatePRItem(item.id, { actualAmount: e.target.value })} + className="h-8 text-xs" + /> + + + + + { + setWbsCodeDialogOpen(open) + if (!open) setSelectedItemForWbs(null) + }} + selectedCode={item.wbsCode ? { + PROJ_NO: '', + WBS_ELMT: item.wbsCode, + WBS_ELMT_NM: item.wbsName || '', + WBS_LVL: '' + } : undefined} + onCodeSelect={(wbsCode) => { + updatePRItem(item.id, { + wbsCode: wbsCode.WBS_ELMT, + wbsName: wbsCode.WBS_ELMT_NM + }) + setWbsCodeDialogOpen(false) + setSelectedItemForWbs(null) + }} + title="WBS 코드 선택" + description="WBS 코드를 선택하세요" + showConfirmButtons={false} + /> + + + + + { + setCostCenterDialogOpen(open) + if (!open) setSelectedItemForCostCenter(null) + }} + selectedCode={item.costCenterCode ? { + KOSTL: item.costCenterCode, + KTEXT: '', + LTEXT: item.costCenterName || '', + DATAB: '', + DATBI: '' + } : undefined} + onCodeSelect={(costCenter) => { + updatePRItem(item.id, { + costCenterCode: costCenter.KOSTL, + costCenterName: costCenter.LTEXT + }) + setCostCenterDialogOpen(false) + setSelectedItemForCostCenter(null) + }} + title="코스트센터 선택" + description="코스트센터를 선택하세요" + showConfirmButtons={false} + /> + + + + + { + setGlAccountDialogOpen(open) + if (!open) setSelectedItemForGlAccount(null) + }} + selectedCode={item.glAccountCode ? { + SAKNR: item.glAccountCode, + FIPEX: '', + TEXT1: item.glAccountName || '' + } : undefined} + onCodeSelect={(glAccount) => { + updatePRItem(item.id, { + glAccountCode: glAccount.SAKNR, + glAccountName: glAccount.TEXT1 + }) + setGlAccountDialogOpen(false) + setSelectedItemForGlAccount(null) + }} + title="GL 계정 선택" + description="GL 계정을 선택하세요" + showConfirmButtons={false} + /> + + + + updatePRItem(item.id, { requestedDeliveryDate: e.target.value })} + className="h-8 text-xs" + /> + +
+ +
+
+
+
+ ) + } + + return ( +
+ + +
+ + + 입찰 품목 목록 + +

+ 입찰 대상 품목들을 관리합니다. 최소 하나의 아이템이 필요하며, 자재그룹코드는 필수입니다 +

+

+ 수량/단위 또는 중량/중량단위를 선택해서 입력하세요 +

+
+
+ + +
+
+ + {/* 내정가 산정 기준 입력 폼 */} +
+ +