"use client"; /** * 수주 등록 컴포넌트 * * - 견적 불러오기 섹션 * - 기본 정보 섹션 * - 수주/배송 정보 섹션 * - 수신처 주소 섹션 * - 비고 섹션 * - 품목 내역 섹션 */ import { useState, useEffect, useCallback } from "react"; import { useDaumPostcode } from "@/hooks/useDaumPostcode"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { FileText, Search, X, Plus, Trash2, Info, } from "lucide-react"; import { toast } from "sonner"; import { ResponsiveFormTemplate, FormSection, } from "@/components/templates/ResponsiveFormTemplate"; import { QuotationSelectDialog, QuotationForSelect, QuotationItem } from "./QuotationSelectDialog"; import { ItemAddDialog, OrderItem } from "./ItemAddDialog"; import { formatAmount } from "@/utils/formatAmount"; import { cn } from "@/lib/utils"; // 수주 폼 데이터 타입 export interface OrderFormData { // 견적 정보 selectedQuotation?: QuotationForSelect; // 기본 정보 clientId: string; clientName: string; siteName: string; manager: string; contact: string; // 수주/배송 정보 expectedShipDate: string; expectedShipDateUndecided: boolean; deliveryRequestDate: string; deliveryRequestDateUndecided: boolean; deliveryMethod: string; shippingCost: string; receiver: string; receiverContact: string; // 수신처 주소 zipCode: string; address: string; addressDetail: string; // 비고 remarks: string; // 품목 내역 items: OrderItem[]; // 금액 정보 subtotal: number; discountRate: number; totalAmount: number; } // 초기 폼 데이터 const INITIAL_FORM: OrderFormData = { clientId: "", clientName: "", siteName: "", manager: "", contact: "", expectedShipDate: "", expectedShipDateUndecided: false, deliveryRequestDate: "", deliveryRequestDateUndecided: false, deliveryMethod: "", shippingCost: "", receiver: "", receiverContact: "", zipCode: "", address: "", addressDetail: "", remarks: "", items: [], subtotal: 0, discountRate: 0, totalAmount: 0, }; // 배송방식 옵션 const DELIVERY_METHODS = [ { value: "direct", label: "직접배차" }, { value: "pickup", label: "상차" }, { value: "courier", label: "택배" }, ]; // 운임비용 옵션 const SHIPPING_COSTS = [ { value: "free", label: "무료" }, { value: "prepaid", label: "선불" }, { value: "collect", label: "착불" }, { value: "negotiable", label: "협의" }, ]; // 샘플 발주처 데이터 const SAMPLE_CLIENTS = [ { id: "C001", name: "태영건설(주)" }, { id: "C002", name: "현대건설(주)" }, { id: "C003", name: "GS건설(주)" }, { id: "C004", name: "대우건설(주)" }, { id: "C005", name: "포스코건설" }, ]; interface OrderRegistrationProps { onBack: () => void; onSave: (formData: OrderFormData) => Promise; initialData?: Partial; isEditMode?: boolean; } // 필드별 에러 타입 interface FieldErrors { clientName?: string; siteName?: string; deliveryRequestDate?: string; receiver?: string; receiverContact?: string; items?: string; } // 필드명 한글 매핑 const FIELD_NAME_MAP: Record = { clientName: "발주처", siteName: "현장명", deliveryRequestDate: "납품요청일", receiver: "수신(반장/업체)", receiverContact: "수신처 연락처", items: "품목 내역", }; export function OrderRegistration({ onBack, onSave, initialData, isEditMode = false, }: OrderRegistrationProps) { const [form, setForm] = useState({ ...INITIAL_FORM, ...initialData, }); const [isQuotationDialogOpen, setIsQuotationDialogOpen] = useState(false); const [isItemDialogOpen, setIsItemDialogOpen] = useState(false); const [isSaving, setIsSaving] = useState(false); const [fieldErrors, setFieldErrors] = useState({}); // Daum 우편번호 서비스 const { openPostcode } = useDaumPostcode({ onComplete: (result) => { setForm((prev) => ({ ...prev, zipCode: result.zonecode, address: result.address, })); }, }); // 금액 계산 useEffect(() => { const subtotal = form.items.reduce((sum, item) => sum + item.amount, 0); const discountAmount = subtotal * (form.discountRate / 100); const totalAmount = subtotal - discountAmount; setForm((prev) => ({ ...prev, subtotal, totalAmount, })); }, [form.items, form.discountRate]); // 견적 선택 핸들러 const handleQuotationSelect = (quotation: QuotationForSelect) => { // 견적 정보로 폼 자동 채우기 (견적에서 가져온 품목은 삭제 불가) const items: OrderItem[] = (quotation.items || []).map((qi: QuotationItem) => ({ id: qi.id, itemCode: qi.itemCode, itemName: qi.itemName, type: qi.type, symbol: qi.symbol, spec: qi.spec, width: 0, height: 0, quantity: qi.quantity, unit: qi.unit, unitPrice: qi.unitPrice, amount: qi.amount, isFromQuotation: true, // 견적에서 가져온 품목 표시 })); setForm((prev) => ({ ...prev, selectedQuotation: quotation, clientName: quotation.client, siteName: quotation.siteName, manager: quotation.manager || "", contact: quotation.contact || "", items, })); toast.success("견적 정보가 불러와졌습니다."); }; // 견적 해제 핸들러 const handleClearQuotation = () => { setForm((prev) => ({ ...prev, selectedQuotation: undefined, // 기본 정보는 유지하고 품목만 초기화할지, 전체 초기화할지 선택 가능 items: [], })); }; // 품목 추가 핸들러 const handleAddItem = (item: OrderItem) => { setForm((prev) => ({ ...prev, items: [...prev.items, item], })); // 품목 에러 초기화 setFieldErrors((prev) => { if (prev.items) { const { items: _, ...rest } = prev; return rest; } return prev; }); toast.success("품목이 추가되었습니다."); }; // 품목 삭제 핸들러 const handleRemoveItem = (itemId: string) => { setForm((prev) => ({ ...prev, items: prev.items.filter((item) => item.id !== itemId), })); }; // 품목 수량 변경 핸들러 const handleQuantityChange = (itemId: string, quantity: number) => { setForm((prev) => ({ ...prev, items: prev.items.map((item) => item.id === itemId ? { ...item, quantity, amount: item.unitPrice * quantity } : item ), })); }; // 유효성 검사 함수 const validateForm = useCallback((): FieldErrors => { const errors: FieldErrors = {}; if (!form.clientName.trim()) { errors.clientName = "발주처를 선택해주세요."; } if (!form.siteName.trim()) { errors.siteName = "현장명을 입력해주세요."; } if (!form.deliveryRequestDate && !form.deliveryRequestDateUndecided) { errors.deliveryRequestDate = "납품요청일을 입력하거나 '미정'을 선택해주세요."; } if (!form.receiver.trim()) { errors.receiver = "수신자명을 입력해주세요."; } if (!form.receiverContact.trim()) { errors.receiverContact = "연락처를 입력해주세요."; } if (form.items.length === 0) { errors.items = "최소 1개 이상의 품목을 추가해주세요."; } return errors; }, [form]); // 필드 에러 초기화 (필드 값 변경 시) const clearFieldError = useCallback((field: keyof FieldErrors) => { setFieldErrors((prev) => { if (prev[field]) { const { [field]: _, ...rest } = prev; return rest; } return prev; }); }, []); // 저장 핸들러 const handleSave = async () => { // 유효성 검사 const errors = validateForm(); setFieldErrors(errors); const errorCount = Object.keys(errors).length; if (errorCount > 0) { toast.error(`입력 내용을 확인해주세요. (${errorCount}개 오류)`); return; } setIsSaving(true); try { await onSave(form); } finally { setIsSaving(false); } }; return ( <> {/* Validation 에러 Alert */} {Object.keys(fieldErrors).length > 0 && (
⚠️
입력 내용을 확인해주세요 ({Object.keys(fieldErrors).length}개 오류)
    {Object.entries(fieldErrors).map(([field, error]) => { const fieldName = FIELD_NAME_MAP[field] || field; return (
  • {fieldName}: {error}
  • ); })}
)} {/* 견적 불러오기 섹션 */}
확정된 견적을 선택하면 정보가 자동으로 채워집니다
{form.selectedQuotation ? (
{form.selectedQuotation.quoteNumber} {form.selectedQuotation.grade} (우량)
{form.selectedQuotation.client} / {form.selectedQuotation.siteName} / {formatAmount(form.selectedQuotation.amount)}원
) : ( )}
{/* 기본 정보 섹션 */}
{fieldErrors.clientName && (

{fieldErrors.clientName}

)}
{ setForm((prev) => ({ ...prev, siteName: e.target.value })); clearFieldError("siteName"); }} className={cn(fieldErrors.siteName && "border-red-500")} /> {fieldErrors.siteName && (

{fieldErrors.siteName}

)}
setForm((prev) => ({ ...prev, manager: e.target.value })) } />
setForm((prev) => ({ ...prev, contact: e.target.value })) } />
{/* 수주/배송 정보 섹션 */}
{/* 출고예정일 */}
setForm((prev) => ({ ...prev, expectedShipDate: e.target.value, })) } disabled={form.expectedShipDateUndecided} className="flex-1" />
setForm((prev) => ({ ...prev, expectedShipDateUndecided: checked as boolean, expectedShipDate: checked ? "" : prev.expectedShipDate, })) } />
{/* 납품요청일 */}
{ setForm((prev) => ({ ...prev, deliveryRequestDate: e.target.value, })); clearFieldError("deliveryRequestDate"); }} disabled={form.deliveryRequestDateUndecided} className={cn("flex-1", fieldErrors.deliveryRequestDate && "border-red-500")} />
{ setForm((prev) => ({ ...prev, deliveryRequestDateUndecided: checked as boolean, deliveryRequestDate: checked ? "" : prev.deliveryRequestDate, })); if (checked) clearFieldError("deliveryRequestDate"); }} />
{fieldErrors.deliveryRequestDate && (

{fieldErrors.deliveryRequestDate}

)}
{/* 배송방식 */}
{/* 운임비용 */}
{/* 수신(반장/업체) */}
{ setForm((prev) => ({ ...prev, receiver: e.target.value })); clearFieldError("receiver"); }} className={cn(fieldErrors.receiver && "border-red-500")} /> {fieldErrors.receiver && (

{fieldErrors.receiver}

)}
{/* 수신처 연락처 */}
{ setForm((prev) => ({ ...prev, receiverContact: e.target.value, })); clearFieldError("receiverContact"); }} className={cn(fieldErrors.receiverContact && "border-red-500")} /> {fieldErrors.receiverContact && (

{fieldErrors.receiverContact}

)}
{/* 수신처 주소 섹션 */}
setForm((prev) => ({ ...prev, zipCode: e.target.value })) } className="w-32" />
setForm((prev) => ({ ...prev, address: e.target.value })) } /> setForm((prev) => ({ ...prev, addressDetail: e.target.value })) } />
{/* 비고 섹션 */}