From ba8cd44a0ed2c613a5f2cee06bfc9bd0f61f21c7 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
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

---
 .../detail/general-contract-items-table.tsx        | 292 +++++++++++++++++++--
 1 file changed, 263 insertions(+), 29 deletions(-)

(limited to 'lib/general-contracts/detail/general-contract-items-table.tsx')

diff --git a/lib/general-contracts/detail/general-contract-items-table.tsx b/lib/general-contracts/detail/general-contract-items-table.tsx
index 1b9a1a06..ed1e5afb 100644
--- a/lib/general-contracts/detail/general-contract-items-table.tsx
+++ b/lib/general-contracts/detail/general-contract-items-table.tsx
@@ -20,15 +20,26 @@ import {
   Package,
   Plus,
   Trash2,
+  FileSpreadsheet,
+  Save,
+  LoaderIcon
 } from 'lucide-react'
 import { toast } from 'sonner'
 import { updateContractItems, getContractItems } from '../service'
-import { Save, LoaderIcon } from 'lucide-react'
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
+import { ProjectSelector } from '@/components/ProjectSelector'
+import { MaterialGroupSelectorDialogSingle } from '@/components/common/material/material-group-selector-dialog-single'
+import { MaterialSearchItem } from '@/lib/material/material-group-service'
 
 interface ContractItem {
   id?: number
+  projectId?: number | null
+  projectName?: string
+  projectCode?: string
   itemCode: string
   itemInfo: string
+  materialGroupCode?: string
+  materialGroupDescription?: string
   specification: string
   quantity: number
   quantityUnit: string
@@ -49,6 +60,9 @@ interface ContractItemsTableProps {
   onTotalAmountChange: (total: number) => void
   availableBudget?: number
   readOnly?: boolean
+  contractScope?: string // 계약확정범위 (단가/금액/물량)
+  deliveryType?: string // 납기종류 (단일납기/분할납기)
+  contractDeliveryDate?: string // 기본정보의 계약납기일
 }
 
 // 통화 목록
@@ -66,12 +80,28 @@ export function ContractItemsTable({
   onItemsChange,
   onTotalAmountChange,
   availableBudget = 0,
-  readOnly = false
+  readOnly = false,
+  contractScope = '',
+  deliveryType = '',
+  contractDeliveryDate = ''
 }: ContractItemsTableProps) {
+  // 계약확정범위에 따른 필드 활성화/비활성화
+  const isQuantityDisabled = contractScope === '단가' || contractScope === '물량'
+  const isTotalAmountDisabled = contractScope === '단가' || contractScope === '물량'
+  // 단일납기인 경우 납기일 필드 비활성화 및 기본값 설정
+  const isDeliveryDateDisabled = deliveryType === '단일납기'
   const [localItems, setLocalItems] = React.useState<ContractItem[]>(items)
   const [isSaving, setIsSaving] = React.useState(false)
   const [isLoading, setIsLoading] = React.useState(false)
   const [isEnabled, setIsEnabled] = React.useState(true)
+  const [showBatchInputDialog, setShowBatchInputDialog] = React.useState(false)
+  const [batchInputData, setBatchInputData] = React.useState({
+    quantity: '',
+    quantityUnit: 'EA',
+    contractDeliveryDate: '',
+    contractCurrency: 'KRW',
+    contractUnitPrice: ''
+  })
 
   // 초기 데이터 로드
   React.useEffect(() => {
@@ -79,21 +109,41 @@ export function ContractItemsTable({
       try {
         setIsLoading(true)
         const fetchedItems = await getContractItems(contractId)
-        const formattedItems = fetchedItems.map(item => ({
-          id: item.id,
-          itemCode: item.itemCode || '',
-          itemInfo: item.itemInfo || '',
-          specification: item.specification || '',
-          quantity: Number(item.quantity) || 0,
-          quantityUnit: item.quantityUnit || 'EA',
-          totalWeight: Number(item.totalWeight) || 0,
-          weightUnit: item.weightUnit || 'KG',
-          contractDeliveryDate: item.contractDeliveryDate || '',
-          contractUnitPrice: Number(item.contractUnitPrice) || 0,
-          contractAmount: Number(item.contractAmount) || 0,
-          contractCurrency: item.contractCurrency || 'KRW',
-          isSelected: false
-        })) as ContractItem[]
+        const formattedItems = fetchedItems.map(item => {
+          // itemInfo에서 자재그룹 정보 파싱 (형식: "자재그룹코드 / 자재그룹명")
+          let materialGroupCode = ''
+          let materialGroupDescription = ''
+          if (item.itemInfo) {
+            const parts = item.itemInfo.split(' / ')
+            if (parts.length >= 2) {
+              materialGroupCode = parts[0].trim()
+              materialGroupDescription = parts.slice(1).join(' / ').trim()
+            } else if (parts.length === 1) {
+              materialGroupCode = parts[0].trim()
+            }
+          }
+          
+          return {
+            id: item.id,
+            projectId: item.projectId || null,
+            projectName: item.projectName || undefined,
+            projectCode: item.projectCode || undefined,
+            itemCode: item.itemCode || '',
+            itemInfo: item.itemInfo || '',
+            materialGroupCode: materialGroupCode || undefined,
+            materialGroupDescription: materialGroupDescription || undefined,
+            specification: item.specification || '',
+            quantity: Number(item.quantity) || 0,
+            quantityUnit: item.quantityUnit || 'EA',
+            totalWeight: Number(item.totalWeight) || 0,
+            weightUnit: item.weightUnit || 'KG',
+            contractDeliveryDate: item.contractDeliveryDate || '',
+            contractUnitPrice: Number(item.contractUnitPrice) || 0,
+            contractAmount: Number(item.contractAmount) || 0,
+            contractCurrency: item.contractCurrency || 'KRW',
+            isSelected: false
+          }
+        }) as ContractItem[]
         setLocalItems(formattedItems as ContractItem[])
         onItemsChange(formattedItems as ContractItem[])
       } catch (error) {
@@ -176,8 +226,11 @@ export function ContractItemsTable({
   // 행 추가
   const addRow = () => {
     const newItem: ContractItem = {
+      projectId: null,
       itemCode: '',
       itemInfo: '',
+      materialGroupCode: '',
+      materialGroupDescription: '',
       specification: '',
       quantity: 0,
       quantityUnit: 'EA', // 기본 수량 단위
@@ -218,6 +271,43 @@ export function ContractItemsTable({
     onItemsChange(updatedItems)
   }
 
+  // 일괄입력 적용
+  const applyBatchInput = () => {
+    if (localItems.length === 0) {
+      toast.error('품목이 없습니다. 먼저 품목을 추가해주세요.')
+      return
+    }
+
+    const updatedItems = localItems.map(item => {
+      const updatedItem = { ...item }
+      
+      if (batchInputData.quantity) {
+        updatedItem.quantity = parseFloat(batchInputData.quantity) || 0
+      }
+      if (batchInputData.quantityUnit) {
+        updatedItem.quantityUnit = batchInputData.quantityUnit
+      }
+      if (batchInputData.contractDeliveryDate) {
+        updatedItem.contractDeliveryDate = batchInputData.contractDeliveryDate
+      }
+      if (batchInputData.contractCurrency) {
+        updatedItem.contractCurrency = batchInputData.contractCurrency
+      }
+      if (batchInputData.contractUnitPrice) {
+        updatedItem.contractUnitPrice = parseFloat(batchInputData.contractUnitPrice) || 0
+        // 단가가 변경되면 계약금액도 재계산
+        updatedItem.contractAmount = updatedItem.contractUnitPrice * updatedItem.quantity
+      }
+      
+      return updatedItem
+    })
+
+    setLocalItems(updatedItems)
+    onItemsChange(updatedItems)
+    setShowBatchInputDialog(false)
+    toast.success('일괄입력이 적용되었습니다.')
+  }
+
 
   // 통화 포맷팅
   const formatCurrency = (amount: number, currency: string = 'KRW') => {
@@ -292,6 +382,104 @@ export function ContractItemsTable({
                       <Plus className="w-4 h-4" />
                       행 추가
                     </Button>
+                    <Dialog open={showBatchInputDialog} onOpenChange={setShowBatchInputDialog}>
+                      <DialogTrigger asChild>
+                        <Button
+                          variant="outline"
+                          size="sm"
+                          disabled={!isEnabled || localItems.length === 0}
+                          className="flex items-center gap-2"
+                        >
+                          <FileSpreadsheet className="w-4 h-4" />
+                          일괄입력
+                        </Button>
+                      </DialogTrigger>
+                      <DialogContent className="max-w-md">
+                        <DialogHeader>
+                          <DialogTitle>품목 정보 일괄입력</DialogTitle>
+                        </DialogHeader>
+                        <div className="space-y-4 py-4">
+                          <div className="flex flex-col gap-2">
+                            <Label htmlFor="batch-quantity">수량</Label>
+                            <Input
+                              id="batch-quantity"
+                              type="number"
+                              value={batchInputData.quantity}
+                              onChange={(e) => setBatchInputData(prev => ({ ...prev, quantity: e.target.value }))}
+                              placeholder="수량 입력 (선택사항)"
+                            />
+                          </div>
+                          <div className="flex flex-col gap-2">
+                            <Label htmlFor="batch-quantity-unit">수량단위</Label>
+                            <Select
+                              value={batchInputData.quantityUnit}
+                              onValueChange={(value) => setBatchInputData(prev => ({ ...prev, quantityUnit: value }))}
+                            >
+                              <SelectTrigger>
+                                <SelectValue />
+                              </SelectTrigger>
+                              <SelectContent>
+                                {QUANTITY_UNITS.map((unit) => (
+                                  <SelectItem key={unit} value={unit}>
+                                    {unit}
+                                  </SelectItem>
+                                ))}
+                              </SelectContent>
+                            </Select>
+                          </div>
+                          <div className="flex flex-col gap-2">
+                            <Label htmlFor="batch-delivery-date">계약납기일</Label>
+                            <Input
+                              id="batch-delivery-date"
+                              type="date"
+                              value={batchInputData.contractDeliveryDate}
+                              onChange={(e) => setBatchInputData(prev => ({ ...prev, contractDeliveryDate: e.target.value }))}
+                            />
+                          </div>
+                          <div className="flex flex-col gap-2">
+                            <Label htmlFor="batch-currency">계약통화</Label>
+                            <Select
+                              value={batchInputData.contractCurrency}
+                              onValueChange={(value) => setBatchInputData(prev => ({ ...prev, contractCurrency: value }))}
+                            >
+                              <SelectTrigger>
+                                <SelectValue />
+                              </SelectTrigger>
+                              <SelectContent>
+                                {CURRENCIES.map((currency) => (
+                                  <SelectItem key={currency} value={currency}>
+                                    {currency}
+                                  </SelectItem>
+                                ))}
+                              </SelectContent>
+                            </Select>
+                          </div>
+                          <div className="flex flex-col gap-2">
+                            <Label htmlFor="batch-unit-price">계약단가</Label>
+                            <Input
+                              id="batch-unit-price"
+                              type="number"
+                              value={batchInputData.contractUnitPrice}
+                              onChange={(e) => setBatchInputData(prev => ({ ...prev, contractUnitPrice: e.target.value }))}
+                              placeholder="계약단가 입력 (선택사항)"
+                            />
+                          </div>
+                          <div className="flex justify-end gap-2 pt-4">
+                            <Button
+                              variant="outline"
+                              onClick={() => setShowBatchInputDialog(false)}
+                            >
+                              취소
+                            </Button>
+                            <Button
+                              onClick={applyBatchInput}
+                            >
+                              적용
+                            </Button>
+                          </div>
+                        </div>
+                      </DialogContent>
+                    </Dialog>
                     <Button
                       variant="outline"
                       size="sm"
@@ -322,8 +510,8 @@ export function ContractItemsTable({
         <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-4">
           <div className="space-y-1">
             <Label className="text-sm font-medium">총 계약금액</Label>
-            <div className="text-lg font-bold text-primary">
-              {formatCurrency(totalAmount, localItems[0]?.contractCurrency || 'KRW')}
+            <div className={`text-lg font-bold ${isTotalAmountDisabled ? 'text-gray-400' : 'text-primary'}`}>
+              {isTotalAmountDisabled ? '-' : formatCurrency(totalAmount, localItems[0]?.contractCurrency || 'KRW')}
             </div>
           </div>
           <div className="space-y-1">
@@ -364,8 +552,10 @@ export function ContractItemsTable({
                     />
                   )}
                 </TableHead>
+                <TableHead className="px-3 py-3 font-semibold">프로젝트</TableHead>
                 <TableHead className="px-3 py-3 font-semibold">품목코드 (PKG No.)</TableHead>
-                <TableHead className="px-3 py-3 font-semibold">Item 정보 (자재그룹 / 자재코드)</TableHead>
+                <TableHead className="px-3 py-3 font-semibold">자재그룹</TableHead>
+                <TableHead className="px-3 py-3 font-semibold">자재내역(자재그룹명)</TableHead>
                 <TableHead className="px-3 py-3 font-semibold">규격</TableHead>
                 <TableHead className="px-3 py-3 font-semibold text-right">수량</TableHead>
                 <TableHead className="px-3 py-3 font-semibold">수량단위</TableHead>
@@ -391,6 +581,21 @@ export function ContractItemsTable({
                       />
                     )}
                   </TableCell>
+                  <TableCell className="px-3 py-3">
+                    {readOnly ? (
+                      <span className="text-sm">{item.projectCode && item.projectName ? `${item.projectCode} - ${item.projectName}` : '-'}</span>
+                    ) : (
+                      <ProjectSelector
+                        selectedProjectId={item.projectId || undefined}
+                        onProjectSelect={(project) => {
+                          updateItem(index, 'projectId', project.id)
+                          updateItem(index, 'projectName', project.projectName)
+                          updateItem(index, 'projectCode', project.projectCode)
+                        }}
+                        placeholder="프로젝트 선택"
+                      />
+                    )}
+                  </TableCell>
                   <TableCell className="px-3 py-3">
                     {readOnly ? (
                       <span className="text-sm">{item.itemCode || '-'}</span>
@@ -406,13 +611,42 @@ export function ContractItemsTable({
                   </TableCell>
                   <TableCell className="px-3 py-3">
                     {readOnly ? (
-                      <span className="text-sm">{item.itemInfo || '-'}</span>
+                      <span className="text-sm">{item.materialGroupCode || '-'}</span>
+                    ) : (
+                      <MaterialGroupSelectorDialogSingle
+                        triggerLabel={item.materialGroupCode || "자재그룹 선택"}
+                        triggerVariant="outline"
+                        selectedMaterial={item.materialGroupCode ? {
+                          materialGroupCode: item.materialGroupCode,
+                          materialGroupDescription: item.materialGroupDescription || '',
+                          displayText: `${item.materialGroupCode} - ${item.materialGroupDescription || ''}`
+                        } : null}
+                        onMaterialSelect={(material) => {
+                          if (material) {
+                            updateItem(index, 'materialGroupCode', material.materialGroupCode)
+                            updateItem(index, 'materialGroupDescription', material.materialGroupDescription)
+                            updateItem(index, 'itemInfo', `${material.materialGroupCode} / ${material.materialGroupDescription}`)
+                          } else {
+                            updateItem(index, 'materialGroupCode', '')
+                            updateItem(index, 'materialGroupDescription', '')
+                            updateItem(index, 'itemInfo', '')
+                          }
+                        }}
+                        title="자재그룹 선택"
+                        description="자재그룹을 검색하고 선택해주세요."
+                      />
+                    )}
+                  </TableCell>
+                  <TableCell className="px-3 py-3">
+                    {readOnly ? (
+                      <span className="text-sm">{item.materialGroupDescription || item.itemInfo || '-'}</span>
                     ) : (
                       <Input
-                        value={item.itemInfo}
-                        onChange={(e) => updateItem(index, 'itemInfo', e.target.value)}
-                        placeholder="Item 정보"
-                        className="h-8 text-sm"
+                        value={item.materialGroupDescription || item.itemInfo || ''}
+                        onChange={(e) => updateItem(index, 'materialGroupDescription', e.target.value)}
+                        placeholder="자재그룹명"
+                        className="h-8 text-sm bg-muted/50"
+                        readOnly
                         disabled={!isEnabled}
                       />
                     )}
@@ -440,7 +674,7 @@ export function ContractItemsTable({
                         onChange={(e) => updateItem(index, 'quantity', parseFloat(e.target.value) || 0)}
                         className="h-8 text-sm text-right"
                         placeholder="0"
-                        disabled={!isEnabled}
+                        disabled={!isEnabled || isQuantityDisabled}
                       />
                     )}
                   </TableCell>
@@ -451,7 +685,7 @@ export function ContractItemsTable({
                       <Select
                         value={item.quantityUnit}
                         onValueChange={(value) => updateItem(index, 'quantityUnit', value)}
-                        disabled={!isEnabled}
+                        disabled={!isEnabled || isQuantityDisabled}
                       >
                         <SelectTrigger className="h-8 text-sm w-20">
                           <SelectValue />
@@ -476,7 +710,7 @@ export function ContractItemsTable({
                         onChange={(e) => updateItem(index, 'totalWeight', parseFloat(e.target.value) || 0)}
                         className="h-8 text-sm text-right"
                         placeholder="0"
-                        disabled={!isEnabled}
+                        disabled={!isEnabled || isQuantityDisabled}
                       />
                     )}
                   </TableCell>
@@ -511,7 +745,7 @@ export function ContractItemsTable({
                         value={item.contractDeliveryDate}
                         onChange={(e) => updateItem(index, 'contractDeliveryDate', e.target.value)}
                         className="h-8 text-sm"
-                        disabled={!isEnabled}
+                        disabled={!isEnabled || isDeliveryDateDisabled}
                       />
                     )}
                   </TableCell>
-- 
cgit v1.2.3

