'use client' import React, { useState } from 'react' import { useSession } from 'next-auth/react' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Textarea } from '@/components/ui/textarea' import { Button } from '@/components/ui/button' import { Save, LoaderIcon } from 'lucide-react' import { updateContractBasicInfo, getContractBasicInfo } from '../service' import { toast } from 'sonner' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { GeneralContract } from '@/db/schema' import { ContractDocuments } from './general-contract-documents' import { getPaymentTermsForSelection, getIncotermsForSelection, getPlaceOfShippingForSelection, getPlaceOfDestinationForSelection } from '@/lib/procurement-select/service' interface ContractBasicInfoProps { contractId: number } export function ContractBasicInfo({ contractId }: ContractBasicInfoProps) { const session = useSession() const [isLoading, setIsLoading] = useState(false) const [contract, setContract] = useState(null) const userId = session.data?.user?.id ? Number(session.data.user.id) : null // 독립적인 상태 관리 const [paymentDeliveryPercent, setPaymentDeliveryPercent] = useState('') // Procurement 데이터 상태들 const [paymentTermsOptions, setPaymentTermsOptions] = useState>([]) const [incotermsOptions, setIncotermsOptions] = useState>([]) const [shippingPlaces, setShippingPlaces] = useState>([]) const [destinationPlaces, setDestinationPlaces] = useState>([]) const [procurementLoading, setProcurementLoading] = useState(false) const [formData, setFormData] = useState({ specificationType: '', specificationManualText: '', unitPriceType: '', warrantyPeriod: { 납품후: { enabled: false, period: 0, maxPeriod: 0 }, 인도후: { enabled: false, period: 0, maxPeriod: 0 }, 작업후: { enabled: false, period: 0, maxPeriod: 0 }, 기타: { enabled: false, period: 0, maxPeriod: 0 }, }, contractAmount: null, currency: 'KRW', linkedPoNumber: '', linkedBidNumber: '', notes: '', // 개별 JSON 필드들 (스키마에 맞게) paymentBeforeDelivery: {} as any, paymentDelivery: '', // varchar 타입 paymentAfterDelivery: {} as any, paymentTerm: '', taxType: '', liquidatedDamages: false, liquidatedDamagesPercent: '', deliveryType: '', deliveryTerm: '', shippingLocation: '', dischargeLocation: '', contractDeliveryDate: '', contractEstablishmentConditions: { regularVendorRegistration: false, projectAward: false, ownerApproval: false, other: false, }, interlockingSystem: '', mandatoryDocuments: { technicalDataAgreement: false, nda: false, basicCompliance: false, safetyHealthAgreement: false, }, contractTerminationConditions: { standardTermination: false, projectNotAwarded: false, other: false, }, }) const [errors] = useState>({}) // 계약 데이터 로드 React.useEffect(() => { const loadContract = async () => { try { console.log('Loading contract with ID:', contractId) const contractData = await getContractBasicInfo(contractId) console.log('Contract data received:', contractData) setContract(contractData as GeneralContract) // JSON 필드들 파싱 (null 체크) - 스키마에 맞게 개별 필드로 접근 const paymentBeforeDelivery = (contractData?.paymentBeforeDelivery && typeof contractData.paymentBeforeDelivery === 'object') ? contractData.paymentBeforeDelivery as any : {} const paymentAfterDelivery = (contractData?.paymentAfterDelivery && typeof contractData.paymentAfterDelivery === 'object') ? contractData.paymentAfterDelivery as any : {} const warrantyPeriod = (contractData?.warrantyPeriod && typeof contractData.warrantyPeriod === 'object') ? contractData.warrantyPeriod as any : {} const contractEstablishmentConditions = (contractData?.contractEstablishmentConditions && typeof contractData.contractEstablishmentConditions === 'object') ? contractData.contractEstablishmentConditions as any : {} const mandatoryDocuments = (contractData?.mandatoryDocuments && typeof contractData.mandatoryDocuments === 'object') ? contractData.mandatoryDocuments as any : {} const contractTerminationConditions = (contractData?.contractTerminationConditions && typeof contractData.contractTerminationConditions === 'object') ? contractData.contractTerminationConditions as any : {} // paymentDelivery에서 퍼센트와 타입 분리 const paymentDeliveryValue = contractData?.paymentDelivery || '' let paymentDeliveryType = '' let paymentDeliveryPercentValue = '' if (paymentDeliveryValue.includes('%')) { const match = paymentDeliveryValue.match(/(\d+)%\s*(.+)/) if (match) { paymentDeliveryPercentValue = match[1] paymentDeliveryType = match[2] } } else { paymentDeliveryType = paymentDeliveryValue } setPaymentDeliveryPercent(paymentDeliveryPercentValue) setFormData({ specificationType: contractData?.specificationType || '', specificationManualText: contractData?.specificationManualText || '', unitPriceType: contractData?.unitPriceType || '', warrantyPeriod: warrantyPeriod || { 납품후: { enabled: false, period: 0, maxPeriod: 0 }, 인도후: { enabled: false, period: 0, maxPeriod: 0 }, 작업후: { enabled: false, period: 0, maxPeriod: 0 }, 기타: { enabled: false, period: 0, maxPeriod: 0 }, }, contractAmount: contractData?.contractAmount || null as number | null, currency: contractData?.currency || 'KRW', linkedPoNumber: contractData?.linkedPoNumber || '', linkedBidNumber: contractData?.linkedBidNumber || '', notes: contractData?.notes || '', // 개별 JSON 필드들 paymentBeforeDelivery: paymentBeforeDelivery || {} as any, paymentDelivery: paymentDeliveryType, // 분리된 타입만 저장 paymentAfterDelivery: paymentAfterDelivery || {} as any, paymentTerm: contractData?.paymentTerm || '', taxType: contractData?.taxType || '', liquidatedDamages: contractData?.liquidatedDamages || false, liquidatedDamagesPercent: contractData?.liquidatedDamagesPercent || '', deliveryType: contractData?.deliveryType || '', deliveryTerm: contractData?.deliveryTerm || '', shippingLocation: contractData?.shippingLocation || '', dischargeLocation: contractData?.dischargeLocation || '', contractDeliveryDate: contractData?.contractDeliveryDate || '', contractEstablishmentConditions: contractEstablishmentConditions || { regularVendorRegistration: false, projectAward: false, ownerApproval: false, other: false, }, interlockingSystem: contractData?.interlockingSystem || '', mandatoryDocuments: mandatoryDocuments || { technicalDataAgreement: false, nda: false, basicCompliance: false, safetyHealthAgreement: false, }, contractTerminationConditions: contractTerminationConditions || { standardTermination: false, projectNotAwarded: false, other: false, }, }) } catch (error) { console.error('Error loading contract:', error) toast.error('계약 정보를 불러오는 중 오류가 발생했습니다.') } } if (contractId) { loadContract() } }, [contractId]) // Procurement 데이터 로드 함수들 const loadPaymentTerms = React.useCallback(async () => { setProcurementLoading(true); try { const data = await getPaymentTermsForSelection(); setPaymentTermsOptions(data); } catch (error) { console.error("Failed to load payment terms:", error); toast.error("결제조건 목록을 불러오는데 실패했습니다."); } finally { setProcurementLoading(false); } }, []); const loadIncoterms = React.useCallback(async () => { setProcurementLoading(true); try { const data = await getIncotermsForSelection(); setIncotermsOptions(data); } catch (error) { console.error("Failed to load incoterms:", error); toast.error("운송조건 목록을 불러오는데 실패했습니다."); } finally { setProcurementLoading(false); } }, []); const loadShippingPlaces = React.useCallback(async () => { setProcurementLoading(true); try { const data = await getPlaceOfShippingForSelection(); setShippingPlaces(data); } catch (error) { console.error("Failed to load shipping places:", error); toast.error("선적지 목록을 불러오는데 실패했습니다."); } finally { setProcurementLoading(false); } }, []); const loadDestinationPlaces = React.useCallback(async () => { setProcurementLoading(true); try { const data = await getPlaceOfDestinationForSelection(); setDestinationPlaces(data); } catch (error) { console.error("Failed to load destination places:", error); toast.error("하역지 목록을 불러오는데 실패했습니다."); } finally { setProcurementLoading(false); } }, []); // 컴포넌트 마운트 시 procurement 데이터 로드 React.useEffect(() => { loadPaymentTerms(); loadIncoterms(); loadShippingPlaces(); loadDestinationPlaces(); }, [loadPaymentTerms, loadIncoterms, loadShippingPlaces, loadDestinationPlaces]); const handleSaveContractInfo = async () => { if (!userId) { toast.error('사용자 정보를 찾을 수 없습니다.') return } try { setIsLoading(true) // 필수값 validation 체크 const validationErrors: string[] = [] if (!formData.specificationType) validationErrors.push('사양') if (!formData.paymentDelivery) validationErrors.push('납품 지급조건') if (!formData.currency) validationErrors.push('계약통화') if (!formData.paymentTerm) validationErrors.push('지불조건') if (!formData.taxType) validationErrors.push('세금조건') if (validationErrors.length > 0) { toast.error(`다음 필수 항목을 입력해주세요: ${validationErrors.join(', ')}`) return } // paymentDelivery와 paymentDeliveryPercent 합쳐서 저장 const dataToSave = { ...formData, paymentDelivery: (formData.paymentDelivery === 'L/C' || formData.paymentDelivery === 'T/T') && paymentDeliveryPercent ? `${paymentDeliveryPercent}% ${formData.paymentDelivery}` : formData.paymentDelivery } await updateContractBasicInfo(contractId, dataToSave, userId as number) toast.success('계약 정보가 저장되었습니다.') } catch (error) { console.error('Error saving contract info:', error) toast.error('계약 정보 저장 중 오류가 발생했습니다.') } finally { setIsLoading(false) } } return ( 계약 기본 정보 기본 정보 지급/인도 조건 추가 조건 계약첨부문서 {/* 기본 정보 탭 */} {/* 보증기간 및 단가유형 */} 보증기간 및 단가유형 {/* 3그리드: 보증기간, 사양, 단가 */}
{/* 보증기간 */}
setFormData(prev => ({ ...prev, warrantyPeriod: { ...prev.warrantyPeriod, 납품후: { ...prev.warrantyPeriod.납품후, enabled: e.target.checked } } }))} className="rounded" />
{formData.warrantyPeriod.납품후?.enabled && (
setFormData(prev => ({ ...prev, warrantyPeriod: { ...prev.warrantyPeriod, 납품후: { ...prev.warrantyPeriod.납품후, period: parseInt(e.target.value) || 0 } } }))} className="w-20 h-8 text-sm" /> 개월, 최대 setFormData(prev => ({ ...prev, warrantyPeriod: { ...prev.warrantyPeriod, 납품후: { ...prev.warrantyPeriod.납품후, maxPeriod: parseInt(e.target.value) || 0 } } }))} className="w-20 h-8 text-sm" /> 개월
)}
setFormData(prev => ({ ...prev, warrantyPeriod: { ...prev.warrantyPeriod, 인도후: { ...prev.warrantyPeriod.인도후, enabled: e.target.checked } } }))} className="rounded" />
{formData.warrantyPeriod.인도후?.enabled && (
setFormData(prev => ({ ...prev, warrantyPeriod: { ...prev.warrantyPeriod, 인도후: { ...prev.warrantyPeriod.인도후, period: parseInt(e.target.value) || 0 } } }))} className="w-20 h-8 text-sm" /> 개월, 최대 setFormData(prev => ({ ...prev, warrantyPeriod: { ...prev.warrantyPeriod, 인도후: { ...prev.warrantyPeriod.인도후, maxPeriod: parseInt(e.target.value) || 0 } } }))} className="w-20 h-8 text-sm" /> 개월
)}
setFormData(prev => ({ ...prev, warrantyPeriod: { ...prev.warrantyPeriod, 작업후: { ...prev.warrantyPeriod.작업후, enabled: e.target.checked } } }))} className="rounded" />
{formData.warrantyPeriod.작업후?.enabled && (
setFormData(prev => ({ ...prev, warrantyPeriod: { ...prev.warrantyPeriod, 작업후: { ...prev.warrantyPeriod.작업후, period: parseInt(e.target.value) || 0 } } }))} className="w-20 h-8 text-sm" /> 개월, 최대 setFormData(prev => ({ ...prev, warrantyPeriod: { ...prev.warrantyPeriod, 작업후: { ...prev.warrantyPeriod.작업후, maxPeriod: parseInt(e.target.value) || 0 } } }))} className="w-20 h-8 text-sm" /> 개월
)}
setFormData(prev => ({ ...prev, warrantyPeriod: { ...prev.warrantyPeriod, 기타: { ...prev.warrantyPeriod.기타, enabled: e.target.checked } } }))} className="rounded" />
{/* 사양 */}
{errors.specificationType && (

사양은 필수값입니다.

)}
{/* 단가 */}
{/* 선택에 따른 폼: vertical로 출력 */} {/* 사양이 수기사양일 때 매뉴얼 텍스트 */} {formData.specificationType === '수기사양' && (