ERP-node/frontend/components/order/OrderRegistrationModal.tsx

525 lines
18 KiB
TypeScript

"use client";
import React, { useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { OrderCustomerSearch } from "./OrderCustomerSearch";
import { OrderItemRepeaterTable } from "./OrderItemRepeaterTable";
import { toast } from "sonner";
import { apiClient } from "@/lib/api/client";
interface OrderRegistrationModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess?: () => void;
}
export function OrderRegistrationModal({
open,
onOpenChange,
onSuccess,
}: OrderRegistrationModalProps) {
// 입력 방식
const [inputMode, setInputMode] = useState<string>("customer_first");
// 판매 유형 (국내/해외)
const [salesType, setSalesType] = useState<string>("domestic");
// 단가 기준 (기준단가/거래처별단가)
const [priceType, setPriceType] = useState<string>("standard");
// 폼 데이터
const [formData, setFormData] = useState<any>({
customerCode: "",
customerName: "",
contactPerson: "",
deliveryDestination: "",
deliveryAddress: "",
deliveryDate: "",
memo: "",
// 무역 정보 (해외 판매 시)
incoterms: "",
paymentTerms: "",
currency: "KRW",
portOfLoading: "",
portOfDischarge: "",
hsCode: "",
});
// 선택된 품목 목록
const [selectedItems, setSelectedItems] = useState<any[]>([]);
// 저장 중
const [isSaving, setIsSaving] = useState(false);
// 저장 처리
const handleSave = async () => {
try {
// 유효성 검사
if (!formData.customerCode) {
toast.error("거래처를 선택해주세요");
return;
}
if (selectedItems.length === 0) {
toast.error("품목을 추가해주세요");
return;
}
setIsSaving(true);
// 수주 등록 API 호출
const orderData: any = {
inputMode,
salesType,
priceType,
customerCode: formData.customerCode,
contactPerson: formData.contactPerson,
deliveryDestination: formData.deliveryDestination,
deliveryAddress: formData.deliveryAddress,
deliveryDate: formData.deliveryDate,
items: selectedItems,
memo: formData.memo,
};
// 해외 판매 시 무역 정보 추가
if (salesType === "export") {
orderData.tradeInfo = {
incoterms: formData.incoterms,
paymentTerms: formData.paymentTerms,
currency: formData.currency,
portOfLoading: formData.portOfLoading,
portOfDischarge: formData.portOfDischarge,
hsCode: formData.hsCode,
};
}
const response = await apiClient.post("/orders", orderData);
if (response.data.success) {
toast.success("수주가 등록되었습니다");
onOpenChange(false);
onSuccess?.();
// 폼 초기화
resetForm();
} else {
toast.error(response.data.message || "수주 등록에 실패했습니다");
}
} catch (error: any) {
console.error("수주 등록 오류:", error);
toast.error(
error.response?.data?.message || "수주 등록 중 오류가 발생했습니다"
);
} finally {
setIsSaving(false);
}
};
// 취소 처리
const handleCancel = () => {
onOpenChange(false);
resetForm();
};
// 폼 초기화
const resetForm = () => {
setInputMode("customer_first");
setSalesType("domestic");
setPriceType("standard");
setFormData({
customerCode: "",
customerName: "",
contactPerson: "",
deliveryDestination: "",
deliveryAddress: "",
deliveryDate: "",
memo: "",
incoterms: "",
paymentTerms: "",
currency: "KRW",
portOfLoading: "",
portOfDischarge: "",
hsCode: "",
});
setSelectedItems([]);
};
// 전체 금액 계산
const totalAmount = selectedItems.reduce(
(sum, item) => sum + (item.amount || 0),
0
);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[95vw] sm:max-w-[1200px] max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-base sm:text-lg"> </DialogTitle>
<DialogDescription className="text-xs sm:text-sm">
</DialogDescription>
</DialogHeader>
<div className="space-y-6">
{/* 상단 셀렉트 박스 3개 */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{/* 입력 방식 */}
<div className="space-y-2">
<Label htmlFor="inputMode" className="text-xs sm:text-sm flex items-center gap-1">
<span className="text-amber-500">📝</span>
</Label>
<Select value={inputMode} onValueChange={setInputMode}>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="입력 방식 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="customer_first"> </SelectItem>
<SelectItem value="quotation"> </SelectItem>
<SelectItem value="unit_price"> </SelectItem>
</SelectContent>
</Select>
</div>
{/* 판매 유형 */}
<div className="space-y-2">
<Label htmlFor="salesType" className="text-xs sm:text-sm flex items-center gap-1">
<span className="text-blue-500">🌏</span>
</Label>
<Select value={salesType} onValueChange={setSalesType}>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="판매 유형 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="domestic"> </SelectItem>
<SelectItem value="export"> </SelectItem>
</SelectContent>
</Select>
</div>
{/* 단가 기준 */}
<div className="space-y-2">
<Label htmlFor="priceType" className="text-xs sm:text-sm flex items-center gap-1">
<span className="text-green-500">💰</span>
</Label>
<Select value={priceType} onValueChange={setPriceType}>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="단가 방식 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="standard"> </SelectItem>
<SelectItem value="customer"> </SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* 거래처 정보 (항상 표시) */}
{inputMode === "customer_first" && (
<div className="rounded-lg border border-gray-200 bg-gray-50/50 p-4 space-y-4">
<div className="flex items-center gap-2 text-sm font-semibold text-gray-700">
<span>🏢</span>
<span> </span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{/* 거래처 */}
<div className="space-y-2">
<Label className="text-xs sm:text-sm"> *</Label>
<OrderCustomerSearch
value={formData.customerCode}
onChange={(code, fullData) => {
setFormData({
...formData,
customerCode: code || "",
customerName: fullData?.customer_name || "",
});
}}
/>
</div>
{/* 담당자 */}
<div className="space-y-2">
<Label htmlFor="contactPerson" className="text-xs sm:text-sm">
</Label>
<Input
id="contactPerson"
placeholder="담당자"
value={formData.contactPerson}
onChange={(e) =>
setFormData({ ...formData, contactPerson: e.target.value })
}
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
{/* 납품처 */}
<div className="space-y-2">
<Label htmlFor="deliveryDestination" className="text-xs sm:text-sm">
</Label>
<Input
id="deliveryDestination"
placeholder="납품처"
value={formData.deliveryDestination}
onChange={(e) =>
setFormData({ ...formData, deliveryDestination: e.target.value })
}
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
{/* 납품장소 */}
<div className="space-y-2">
<Label htmlFor="deliveryAddress" className="text-xs sm:text-sm">
</Label>
<Input
id="deliveryAddress"
placeholder="납품장소"
value={formData.deliveryAddress}
onChange={(e) =>
setFormData({ ...formData, deliveryAddress: e.target.value })
}
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
</div>
</div>
)}
{inputMode === "quotation" && (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-2">
<Label className="text-xs sm:text-sm"> *</Label>
<Input
placeholder="견대 번호를 입력하세요"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
</div>
)}
{inputMode === "unit_price" && (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-2">
<Label className="text-xs sm:text-sm"> </Label>
<Input
placeholder="단가 정보 입력"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
</div>
)}
{/* 추가된 품목 */}
<div className="space-y-2">
<Label className="text-xs sm:text-sm"> </Label>
<OrderItemRepeaterTable
value={selectedItems}
onChange={setSelectedItems}
/>
</div>
{/* 전체 금액 표시 */}
{selectedItems.length > 0 && (
<div className="flex justify-end">
<div className="text-sm sm:text-base font-semibold">
: {totalAmount.toLocaleString()}
</div>
</div>
)}
{/* 무역 정보 (해외 판매 시에만 표시) */}
{salesType === "export" && (
<div className="rounded-lg border border-blue-200 bg-blue-50/50 p-4 space-y-4">
<div className="flex items-center gap-2 text-sm font-semibold text-blue-700">
<span>🌏</span>
<span> </span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{/* 인코텀즈 */}
<div className="space-y-2">
<Label htmlFor="incoterms" className="text-xs sm:text-sm">
</Label>
<Select
value={formData.incoterms}
onValueChange={(value) =>
setFormData({ ...formData, incoterms: value })
}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="EXW">EXW</SelectItem>
<SelectItem value="FOB">FOB</SelectItem>
<SelectItem value="CIF">CIF</SelectItem>
<SelectItem value="DDP">DDP</SelectItem>
<SelectItem value="DAP">DAP</SelectItem>
</SelectContent>
</Select>
</div>
{/* 결제 조건 */}
<div className="space-y-2">
<Label htmlFor="paymentTerms" className="text-xs sm:text-sm">
</Label>
<Select
value={formData.paymentTerms}
onValueChange={(value) =>
setFormData({ ...formData, paymentTerms: value })
}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="advance"></SelectItem>
<SelectItem value="cod"></SelectItem>
<SelectItem value="lc">(L/C)</SelectItem>
<SelectItem value="net30">NET 30</SelectItem>
<SelectItem value="net60">NET 60</SelectItem>
</SelectContent>
</Select>
</div>
{/* 통화 */}
<div className="space-y-2">
<Label htmlFor="currency" className="text-xs sm:text-sm">
</Label>
<Select
value={formData.currency}
onValueChange={(value) =>
setFormData({ ...formData, currency: value })
}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="통화 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="KRW">KRW ()</SelectItem>
<SelectItem value="USD">USD ()</SelectItem>
<SelectItem value="EUR">EUR ()</SelectItem>
<SelectItem value="JPY">JPY ()</SelectItem>
<SelectItem value="CNY">CNY ()</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{/* 선적항 */}
<div className="space-y-2">
<Label htmlFor="portOfLoading" className="text-xs sm:text-sm">
</Label>
<Input
id="portOfLoading"
placeholder="선적항"
value={formData.portOfLoading}
onChange={(e) =>
setFormData({ ...formData, portOfLoading: e.target.value })
}
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
{/* 도착항 */}
<div className="space-y-2">
<Label htmlFor="portOfDischarge" className="text-xs sm:text-sm">
</Label>
<Input
id="portOfDischarge"
placeholder="도착항"
value={formData.portOfDischarge}
onChange={(e) =>
setFormData({ ...formData, portOfDischarge: e.target.value })
}
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
{/* HS Code */}
<div className="space-y-2">
<Label htmlFor="hsCode" className="text-xs sm:text-sm">
HS Code
</Label>
<Input
id="hsCode"
placeholder="HS Code"
value={formData.hsCode}
onChange={(e) =>
setFormData({ ...formData, hsCode: e.target.value })
}
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
</div>
</div>
)}
{/* 메모 */}
<div className="space-y-2">
<Label htmlFor="memo" className="text-xs sm:text-sm">
</Label>
<Textarea
id="memo"
placeholder="메모를 입력하세요"
value={formData.memo}
onChange={(e) =>
setFormData({ ...formData, memo: e.target.value })
}
className="text-xs sm:text-sm"
rows={3}
/>
</div>
</div>
<DialogFooter className="gap-2 sm:gap-0">
<Button
variant="outline"
onClick={handleCancel}
disabled={isSaving}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
</Button>
<Button
onClick={handleSave}
disabled={isSaving}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
{isSaving ? "저장 중..." : "저장"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}