'use client'; /** * 출하 등록 페이지 * API 연동 완료 (2025-12-26) * IntegratedDetailTemplate 마이그레이션 (2025-01-20) */ import { useState, useCallback, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { getTodayString } from '@/utils/date'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; import { shipmentCreateConfig } from './shipmentConfig'; import { createShipment, getLotOptions, getLogisticsOptions, getVehicleTonnageOptions, } from './actions'; import type { ShipmentCreateFormData, ShipmentPriority, DeliveryMethod, LotOption, LogisticsOption, VehicleTonnageOption, } from './types'; import { isNextRedirectError } from '@/lib/utils/redirect-error'; import { toast } from 'sonner'; import { useDevFill } from '@/components/dev'; import { generateShipmentData } from '@/components/dev/generators/shipmentData'; // 고정 옵션 (클라이언트에서 관리) const priorityOptions: { value: ShipmentPriority; label: string }[] = [ { value: 'urgent', label: '긴급' }, { value: 'normal', label: '일반' }, { value: 'low', label: '낮음' }, ]; const deliveryMethodOptions: { value: DeliveryMethod; label: string }[] = [ { value: 'pickup', label: '상차 (물류업체)' }, { value: 'direct', label: '직접배송 (자체)' }, { value: 'logistics', label: '물류사' }, ]; export function ShipmentCreate() { const router = useRouter(); // 폼 상태 const [formData, setFormData] = useState({ lotNo: '', scheduledDate: getTodayString(), priority: 'normal', deliveryMethod: 'pickup', logisticsCompany: '', vehicleTonnage: '', loadingTime: '', loadingManager: '', remarks: '', }); // API 옵션 데이터 상태 const [lotOptions, setLotOptions] = useState([]); const [logisticsOptions, setLogisticsOptions] = useState([]); const [vehicleTonnageOptions, setVehicleTonnageOptions] = useState([]); // 로딩/에러 상태 const [isLoading, setIsLoading] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); const [validationErrors, setValidationErrors] = useState([]); // 옵션 데이터 로드 const loadOptions = useCallback(async () => { setIsLoading(true); setError(null); try { const [lotsResult, logisticsResult, tonnageResult] = await Promise.all([ getLotOptions(), getLogisticsOptions(), getVehicleTonnageOptions(), ]); if (lotsResult.success && lotsResult.data) { setLotOptions(lotsResult.data); } if (logisticsResult.success && logisticsResult.data) { setLogisticsOptions(logisticsResult.data); } if (tonnageResult.success && tonnageResult.data) { setVehicleTonnageOptions(tonnageResult.data); } } catch (err) { if (isNextRedirectError(err)) throw err; console.error('[ShipmentCreate] loadOptions error:', err); setError('옵션 데이터를 불러오는 중 오류가 발생했습니다.'); } finally { setIsLoading(false); } }, []); // 옵션 로드 useEffect(() => { loadOptions(); }, [loadOptions]); // DevToolbar 자동 채우기 useDevFill( 'shipment', useCallback(() => { // lotOptions를 generateShipmentData에 전달하기 위해 변환 const lotOptionsForGenerator = lotOptions.map(o => ({ lotNo: o.value, customerName: o.customerName, siteName: o.siteName, })); const logisticsOptionsForGenerator = logisticsOptions.map(o => ({ id: o.value, name: o.label, })); const tonnageOptionsForGenerator = vehicleTonnageOptions.map(o => ({ value: o.value, label: o.label, })); const sampleData = generateShipmentData({ lotOptions: lotOptionsForGenerator, logisticsOptions: logisticsOptionsForGenerator, tonnageOptions: tonnageOptionsForGenerator, }); setFormData(sampleData); toast.success('[Dev] 출하 폼이 자동으로 채워졌습니다.'); }, [lotOptions, logisticsOptions, vehicleTonnageOptions]) ); // 폼 입력 핸들러 const handleInputChange = (field: keyof ShipmentCreateFormData, value: string) => { setFormData(prev => ({ ...prev, [field]: value })); // 입력 시 에러 클리어 if (validationErrors.length > 0) { setValidationErrors([]); } }; // 취소 const handleCancel = useCallback(() => { router.push('/ko/outbound/shipments'); }, [router]); // validation 체크 const validateForm = (): boolean => { const errors: string[] = []; // 필수 필드 체크 if (!formData.lotNo) { errors.push('로트번호는 필수 선택 항목입니다.'); } if (!formData.scheduledDate) { errors.push('출고예정일은 필수 입력 항목입니다.'); } if (!formData.priority) { errors.push('출고 우선순위는 필수 선택 항목입니다.'); } if (!formData.deliveryMethod) { errors.push('배송방식은 필수 선택 항목입니다.'); } setValidationErrors(errors); return errors.length === 0; }; // 저장 const handleSubmit = useCallback(async () => { // validation 체크 if (!validateForm()) { return; } setIsSubmitting(true); try { const result = await createShipment(formData); if (result.success) { router.push('/ko/outbound/shipments'); } else { setValidationErrors([result.error || '출하 등록에 실패했습니다.']); } } catch (err) { if (isNextRedirectError(err)) throw err; console.error('[ShipmentCreate] handleSubmit error:', err); setValidationErrors(['저장 중 오류가 발생했습니다.']); } finally { setIsSubmitting(false); } }, [formData, router]); // 폼 컨텐츠 렌더링 const renderFormContent = useCallback(() => (
{/* Validation 에러 표시 */} {validationErrors.length > 0 && (
⚠️
입력 내용을 확인해주세요 ({validationErrors.length}개 오류)
    {validationErrors.map((error, index) => (
  • {error}
  • ))}
)} {/* 수주 선택 */} 수주 선택
{/* 출고 정보 */} 출고 정보
handleInputChange('scheduledDate', e.target.value)} disabled={isSubmitting} />
{/* 상차 (물류업체) - 배송방식이 상차 또는 물류사일 때 표시 */} {(formData.deliveryMethod === 'pickup' || formData.deliveryMethod === 'logistics') && ( 상차 (물류업체)
handleInputChange('loadingTime', e.target.value)} placeholder="연도. 월. 일. -- --:--" disabled={isSubmitting} />

물류업체와 입차시간 확정 후 입력하세요.

)}
handleInputChange('loadingManager', e.target.value)} placeholder="상차 작업 담당자명" disabled={isSubmitting} />