diff options
Diffstat (limited to 'lib/general-contracts/detail/general-contract-items-table.tsx')
| -rw-r--r-- | lib/general-contracts/detail/general-contract-items-table.tsx | 117 |
1 files changed, 94 insertions, 23 deletions
diff --git a/lib/general-contracts/detail/general-contract-items-table.tsx b/lib/general-contracts/detail/general-contract-items-table.tsx index e5fc6cf2..4f74cfbb 100644 --- a/lib/general-contracts/detail/general-contract-items-table.tsx +++ b/lib/general-contracts/detail/general-contract-items-table.tsx @@ -32,6 +32,7 @@ import { MaterialGroupSelectorDialogSingle } from '@/components/common/material/ import { MaterialSearchItem } from '@/lib/material/material-group-service' import { ProcurementItemSelectorDialogSingle } from '@/components/common/selectors/procurement-item/procurement-item-selector-dialog-single' import { ProcurementSearchItem } from '@/components/common/selectors/procurement-item/procurement-item-service' +import { cn } from '@/lib/utils' interface ContractItem { id?: number @@ -43,12 +44,12 @@ interface ContractItem { materialGroupCode?: string materialGroupDescription?: string specification: string - quantity: number + quantity: number | string // number | string으로 변경하여 입력 중 포맷팅 지원 quantityUnit: string - totalWeight: number + totalWeight: number | string // number | string으로 변경하여 입력 중 포맷팅 지원 weightUnit: string contractDeliveryDate: string - contractUnitPrice: number + contractUnitPrice: number | string // number | string으로 변경하여 입력 중 포맷팅 지원 contractAmount: number contractCurrency: string isSelected?: boolean @@ -105,6 +106,34 @@ export function ContractItemsTable({ contractUnitPrice: '' }) + // 천단위 콤마 포맷팅 헬퍼 함수들 + const formatNumberWithCommas = (value: string | number | null | undefined): string => { + if (value === null || value === undefined || value === '') return '' + const str = value.toString() + const parts = str.split('.') + const integerPart = parts[0].replace(/,/g, '') + + // 정수부가 비어있거나 '-' 만 있는 경우 처리 + if (integerPart === '' || integerPart === '-') { + return str + } + + const num = parseFloat(integerPart) + if (isNaN(num)) return str + + const formattedInt = num.toLocaleString() + + if (parts.length > 1) { + return `${formattedInt}.${parts[1]}` + } + + return formattedInt + } + + const parseNumberFromCommas = (value: string): string => { + return value.replace(/,/g, '') + } + // 초기 데이터 로드 React.useEffect(() => { const loadItems = async () => { @@ -125,6 +154,8 @@ export function ContractItemsTable({ } } + // number 타입을 string으로 변환하지 않고 일단 그대로 둠 (렌더링 시 포맷팅) + // 단, 입력 중 편의를 위해 string이 들어올 수 있으므로 ContractItem 타입 변경함 return { id: item.id, projectId: item.projectId || null, @@ -174,8 +205,17 @@ export function ContractItemsTable({ // validation 체크 const errors: string[] = [] - for (let index = 0; index < localItems.length; index++) { - const item = localItems[index] + // 저장 시 number로 변환된 데이터 준비 + const itemsToSave = localItems.map(item => ({ + ...item, + quantity: parseFloat(item.quantity.toString().replace(/,/g, '')) || 0, + totalWeight: parseFloat(item.totalWeight.toString().replace(/,/g, '')) || 0, + contractUnitPrice: parseFloat(item.contractUnitPrice.toString().replace(/,/g, '')) || 0, + contractAmount: parseFloat(item.contractAmount.toString().replace(/,/g, '')) || 0, + })); + + for (let index = 0; index < itemsToSave.length; index++) { + const item = itemsToSave[index] // if (!item.itemCode) errors.push(`${index + 1}번째 품목의 품목코드`) if (!item.itemInfo) errors.push(`${index + 1}번째 품목의 Item 정보`) if (!item.quantity || item.quantity <= 0) errors.push(`${index + 1}번째 품목의 수량`) @@ -188,7 +228,7 @@ export function ContractItemsTable({ return } - await updateContractItems(contractId, localItems as any) + await updateContractItems(contractId, itemsToSave as any) toast.success('품목정보가 저장되었습니다.') } catch (error) { console.error('Error saving contract items:', error) @@ -199,9 +239,18 @@ export function ContractItemsTable({ } // 총 금액 계산 - const totalAmount = localItems.reduce((sum, item) => sum + item.contractAmount, 0) - const totalQuantity = localItems.reduce((sum, item) => sum + item.quantity, 0) - const totalUnitPrice = localItems.reduce((sum, item) => sum + item.contractUnitPrice, 0) + const totalAmount = localItems.reduce((sum, item) => { + const amount = parseFloat(item.contractAmount.toString().replace(/,/g, '')) || 0 + return sum + amount + }, 0) + const totalQuantity = localItems.reduce((sum, item) => { + const quantity = parseFloat(item.quantity.toString().replace(/,/g, '')) || 0 + return sum + quantity + }, 0) + const totalUnitPrice = localItems.reduce((sum, item) => { + const unitPrice = parseFloat(item.contractUnitPrice.toString().replace(/,/g, '')) || 0 + return sum + unitPrice + }, 0) const amountDifference = availableBudget - totalAmount const budgetRatio = availableBudget > 0 ? (totalAmount / availableBudget) * 100 : 0 @@ -213,12 +262,14 @@ export function ContractItemsTable({ // 아이템 업데이트 const updateItem = (index: number, field: keyof ContractItem, value: string | number | boolean | undefined) => { const updatedItems = [...localItems] - updatedItems[index] = { ...updatedItems[index], [field]: value } + const updatedItem = { ...updatedItems[index], [field]: value } + updatedItems[index] = updatedItem // 단가나 수량이 변경되면 금액 자동 계산 if (field === 'contractUnitPrice' || field === 'quantity') { - const item = updatedItems[index] - updatedItems[index].contractAmount = item.contractUnitPrice * item.quantity + const quantity = parseFloat(updatedItem.quantity.toString().replace(/,/g, '')) || 0 + const unitPrice = parseFloat(updatedItem.contractUnitPrice.toString().replace(/,/g, '')) || 0 + updatedItem.contractAmount = unitPrice * quantity } setLocalItems(updatedItems) @@ -326,7 +377,8 @@ export function ContractItemsTable({ if (batchInputData.contractUnitPrice) { updatedItem.contractUnitPrice = parseFloat(batchInputData.contractUnitPrice) || 0 // 단가가 변경되면 계약금액도 재계산 - updatedItem.contractAmount = updatedItem.contractUnitPrice * updatedItem.quantity + const quantity = parseFloat(updatedItem.quantity.toString().replace(/,/g, '')) || 0 + updatedItem.contractAmount = (parseFloat(batchInputData.contractUnitPrice) || 0) * quantity } return updatedItem @@ -712,14 +764,23 @@ export function ContractItemsTable({ )} </TableCell> */} <TableCell className="px-3 py-3"> + {readOnly ? ( + <span className="text-sm text-right">{item.quantity.toLocaleString()}</span> + ) : ( <Input - type="number" - value={item.quantity} - onChange={(e) => updateItem(index, 'quantity', parseFloat(e.target.value) || 0)} + type="text" + value={formatNumberWithCommas(item.quantity)} + onChange={(e) => { + const val = parseNumberFromCommas(e.target.value) + if (val === '' || /^-?\d*\.?\d*$/.test(val)) { + updateItem(index, 'quantity', val) + } + }} className="h-8 text-sm text-right" placeholder="0" - disabled={!isEnabled} + disabled={!isEnabled || isQuantityDisabled} /> + )} </TableCell> <TableCell className="px-3 py-3"> {readOnly ? ( @@ -748,9 +809,14 @@ export function ContractItemsTable({ <span className="text-sm text-right">{item.totalWeight.toLocaleString()}</span> ) : ( <Input - type="number" - value={item.totalWeight} - onChange={(e) => updateItem(index, 'totalWeight', parseFloat(e.target.value) || 0)} + type="text" + value={formatNumberWithCommas(item.totalWeight)} + onChange={(e) => { + const val = parseNumberFromCommas(e.target.value) + if (val === '' || /^-?\d*\.?\d*$/.test(val)) { + updateItem(index, 'totalWeight', val) + } + }} className="h-8 text-sm text-right" placeholder="0" disabled={!isEnabled || isQuantityDisabled} @@ -797,9 +863,14 @@ export function ContractItemsTable({ <span className="text-sm text-right">{item.contractUnitPrice.toLocaleString()}</span> ) : ( <Input - type="number" - value={item.contractUnitPrice} - onChange={(e) => updateItem(index, 'contractUnitPrice', parseFloat(e.target.value) || 0)} + type="text" + value={formatNumberWithCommas(item.contractUnitPrice)} + onChange={(e) => { + const val = parseNumberFromCommas(e.target.value) + if (val === '' || /^-?\d*\.?\d*$/.test(val)) { + updateItem(index, 'contractUnitPrice', val) + } + }} className="h-8 text-sm text-right" placeholder="0" disabled={!isEnabled} |
