"use client" import * as React from "react" import { useState, useEffect } from "react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" import { toast } from "sonner" import { useRouter } from "next/navigation" import { CalendarIcon, Save, Send, ArrowLeft } from "lucide-react" import { Button } from "@/components/ui/button" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Separator } from "@/components/ui/separator" import { DatePicker } from "@/components/ui/date-picker" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Skeleton } from "@/components/ui/skeleton" import { formatCurrency, formatDate } from "@/lib/utils" import { updateTechSalesVendorQuotation, submitTechSalesVendorQuotation, fetchCurrencies } from "../service" // 견적서 폼 스키마 (techsales용 단순화) const quotationFormSchema = z.object({ currency: z.string().min(1, "통화를 선택해주세요"), totalPrice: z.string().min(1, "총액을 입력해주세요"), validUntil: z.date({ required_error: "견적 유효기간을 선택해주세요", invalid_type_error: "유효한 날짜를 선택해주세요", }), remark: z.string().optional(), }) type QuotationFormValues = z.infer // 통화 타입 interface Currency { code: string name: string } // 이 컴포넌트에 전달되는 견적서 데이터 타입 (techsales용 단순화) interface TechSalesVendorQuotation { id: number rfqId: number vendorId: number quotationCode: string | null quotationVersion: number | null totalPrice: string | null currency: string | null validUntil: Date | null status: "Draft" | "Submitted" | "Revised" | "Rejected" | "Accepted" remark: string | null rejectionReason: string | null submittedAt: Date | null acceptedAt: Date | null createdAt: Date updatedAt: Date rfq: { id: number rfqCode: string | null dueDate: Date | null status: string | null materialCode: string | null remark: string | null projectSnapshot?: { pspid?: string projNm?: string sector?: string projMsrm?: number kunnr?: string kunnrNm?: string ptypeNm?: string } | null seriesSnapshot?: Array<{ pspid: string sersNo: string scDt?: string klDt?: string lcDt?: string dlDt?: string dockNo?: string dockNm?: string projNo?: string post1?: string }> | null item?: { id: number itemCode: string | null itemName: string | null } | null biddingProject?: { id: number pspid: string | null projNm: string | null } | null createdByUser?: { id: number name: string | null email: string | null } | null } vendor: { id: number vendorName: string vendorCode: string | null } } interface TechSalesQuotationEditorProps { quotation: TechSalesVendorQuotation } export default function TechSalesQuotationEditor({ quotation }: TechSalesQuotationEditorProps) { const router = useRouter() const [isSubmitting, setIsSubmitting] = useState(false) const [isSaving, setIsSaving] = useState(false) const [currencies, setCurrencies] = useState([]) const [loadingCurrencies, setLoadingCurrencies] = useState(true) // 폼 초기화 const form = useForm({ resolver: zodResolver(quotationFormSchema), defaultValues: { currency: quotation.currency || "USD", totalPrice: quotation.totalPrice || "", validUntil: quotation.validUntil || undefined, remark: quotation.remark || "", }, }) // 통화 목록 로드 useEffect(() => { const loadCurrencies = async () => { try { const { data, error } = await fetchCurrencies() if (error) { toast.error("통화 목록을 불러오는데 실패했습니다") return } setCurrencies(data || []) } catch (error) { console.error("Error loading currencies:", error) toast.error("통화 목록을 불러오는데 실패했습니다") } finally { setLoadingCurrencies(false) } } loadCurrencies() }, []) // 마감일 확인 const isBeforeDueDate = () => { if (!quotation.rfq.dueDate) return true return new Date() <= new Date(quotation.rfq.dueDate) } // 편집 가능 여부 확인 const isEditable = () => { return quotation.status === "Draft" || quotation.status === "Rejected" } // 제출 가능 여부 확인 const isSubmitReady = () => { const values = form.getValues() return values.currency && values.totalPrice && parseFloat(values.totalPrice) > 0 && values.validUntil && isBeforeDueDate() } // 저장 핸들러 const handleSave = async () => { if (!isEditable()) { toast.error("편집할 수 없는 상태입니다") return } setIsSaving(true) try { const values = form.getValues() const { data, error } = await updateTechSalesVendorQuotation({ id: quotation.id, currency: values.currency, totalPrice: values.totalPrice, validUntil: values.validUntil, remark: values.remark, updatedBy: quotation.vendorId, // 임시로 vendorId 사용 }) if (error) { toast.error(error) return } toast.success("견적서가 저장되었습니다") router.refresh() } catch (error) { console.error("Error saving quotation:", error) toast.error("견적서 저장 중 오류가 발생했습니다") } finally { setIsSaving(false) } } // 제출 핸들러 const handleSubmit = async () => { if (!isEditable()) { toast.error("제출할 수 없는 상태입니다") return } if (!isSubmitReady()) { toast.error("필수 항목을 모두 입력해주세요") return } if (!isBeforeDueDate()) { toast.error("마감일이 지났습니다") return } setIsSubmitting(true) try { const values = form.getValues() const { data, error } = await submitTechSalesVendorQuotation({ id: quotation.id, currency: values.currency, totalPrice: values.totalPrice, validUntil: values.validUntil, remark: values.remark, updatedBy: quotation.vendorId, // 임시로 vendorId 사용 }) if (error) { toast.error(error) return } toast.success("견적서가 제출되었습니다") router.push("/ko/partners/techsales/rfq-ship") } catch (error) { console.error("Error submitting quotation:", error) toast.error("견적서 제출 중 오류가 발생했습니다") } finally { setIsSubmitting(false) } } // 상태 배지 const getStatusBadge = (status: string) => { const statusConfig = { "Draft": { label: "초안", variant: "secondary" as const }, "Submitted": { label: "제출됨", variant: "default" as const }, "Revised": { label: "수정됨", variant: "outline" as const }, "Rejected": { label: "반려됨", variant: "destructive" as const }, "Accepted": { label: "승인됨", variant: "success" as const }, } const config = statusConfig[status as keyof typeof statusConfig] || { label: status, variant: "secondary" as const } return {config.label} } return (
{/* 헤더 */}

기술영업 견적서

RFQ: {quotation.rfq.rfqCode} | {getStatusBadge(quotation.status)}

{isEditable() && ( <> )}
{/* 왼쪽: RFQ 정보 */}
{/* RFQ 기본 정보 */} RFQ 정보

{quotation.rfq.rfqCode}

{quotation.rfq.materialCode || "N/A"}

{quotation.rfq.item?.itemName || "N/A"}

{quotation.rfq.dueDate ? formatDate(quotation.rfq.dueDate) : "N/A"}

{quotation.rfq.remark && (

{quotation.rfq.remark}

)}
{/* 프로젝트 정보 */} {quotation.rfq.projectSnapshot && ( 프로젝트 정보

{quotation.rfq.projectSnapshot.pspid}

{quotation.rfq.projectSnapshot.projNm || "N/A"}

{quotation.rfq.projectSnapshot.ptypeNm || "N/A"}

{quotation.rfq.projectSnapshot.projMsrm || "N/A"}

{quotation.rfq.projectSnapshot.kunnrNm || "N/A"}

)} {/* 시리즈 정보 */} {quotation.rfq.seriesSnapshot && quotation.rfq.seriesSnapshot.length > 0 && ( 시리즈 일정
{quotation.rfq.seriesSnapshot.map((series, index) => (
시리즈 {series.sersNo}
{series.klDt && (
K/L: {formatDate(series.klDt)}
)} {series.dlDt && (
인도: {formatDate(series.dlDt)}
)}
))}
)}
{/* 오른쪽: 견적서 입력 폼 */}
견적서 작성 총액 기반으로 견적을 작성해주세요.
{/* 통화 선택 */} ( 통화 * )} /> {/* 총액 입력 */} ( 총액 * )} /> {/* 유효기간 */} ( 견적 유효기간 * )} /> {/* 비고 */} ( 비고