From e246459a0894c158de0c1e2fdf898d9e9d9795a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Tue, 27 Jan 2026 17:41:16 +0900 Subject: [PATCH] =?UTF-8?q?[WEB]=20feat(OrderManagement):=20=EA=B2=AC?= =?UTF-8?q?=EC=A0=81=EC=84=9C=20=EA=B8=B0=EB=B0=98=20=EC=88=98=EC=A3=BC=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - page.tsx: quoteId 파라미터로 견적 데이터 자동 로드 - actions.ts: getQuoteByIdForSelect 함수 추가 - index.ts: getQuoteByIdForSelect export 추가 - quotes/actions.ts: QuotationForSelect, QuotationItem 타입 export --- .../sales/order-management-sales/new/page.tsx | 89 ++++++++++++++++++- src/components/orders/actions.ts | 44 +++++++++ src/components/orders/index.ts | 5 +- src/components/quotes/actions.ts | 5 +- 4 files changed, 138 insertions(+), 5 deletions(-) diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/new/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/new/page.tsx index c5e76adb..27b50635 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/new/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/new/page.tsx @@ -3,14 +3,52 @@ /** * 수주 등록 페이지 * API 연동 완료 (2025-01-08) + * + * quoteId 파라미터 지원 (2026-01-27) + * - /sales/order-management-sales/new?quoteId=123 형태로 접근 시 + * - 해당 견적 정보를 자동으로 불러와 폼에 채움 */ -import { useRouter } from "next/navigation"; -import { OrderRegistration, OrderFormData, createOrder } from "@/components/orders"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useEffect, useState } from "react"; +import { OrderRegistration, OrderFormData, createOrder, getQuoteByIdForSelect } from "@/components/orders"; +import type { QuotationForSelect, QuotationItem } from "@/components/orders/actions"; import { toast } from "sonner"; +import { Loader2 } from "lucide-react"; export default function OrderNewPage() { const router = useRouter(); + const searchParams = useSearchParams(); + const quoteId = searchParams.get("quoteId"); + + const [initialQuotation, setInitialQuotation] = useState(); + const [isLoading, setIsLoading] = useState(!!quoteId); + + // quoteId가 있으면 견적 데이터 로드 + useEffect(() => { + const loadQuoteData = async () => { + if (!quoteId) return; + + setIsLoading(true); + try { + const result = await getQuoteByIdForSelect(quoteId); + + if (result.success && result.data) { + setInitialQuotation(result.data); + toast.success("견적 정보가 불러와졌습니다."); + } else { + toast.error(result.error || "견적 정보를 불러오는데 실패했습니다."); + } + } catch (error) { + console.error("Error loading quote:", error); + toast.error("견적 정보를 불러오는데 실패했습니다."); + } finally { + setIsLoading(false); + } + }; + + loadQuoteData(); + }, [quoteId]); const handleBack = () => { router.push("/sales/order-management-sales"); @@ -32,5 +70,50 @@ export default function OrderNewPage() { } }; - return ; + // 로딩 중일 때 로딩 표시 + if (isLoading) { + return ( +
+
+ +

견적 정보를 불러오는 중...

+
+
+ ); + } + + // initialData 생성 - 견적 데이터가 있으면 변환하여 전달 + const initialData: Partial | undefined = initialQuotation + ? { + selectedQuotation: initialQuotation, + clientId: initialQuotation.clientId || "", + clientName: initialQuotation.client, + siteName: initialQuotation.siteName, + manager: initialQuotation.manager || "", + contact: initialQuotation.contact || "", + items: (initialQuotation.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, + })), + } + : undefined; + + return ( + + ); } \ No newline at end of file diff --git a/src/components/orders/actions.ts b/src/components/orders/actions.ts index 049af14a..28da348a 100644 --- a/src/components/orders/actions.ts +++ b/src/components/orders/actions.ts @@ -1214,6 +1214,50 @@ export async function revertOrderConfirmation(orderId: string): Promise<{ } } +/** + * 수주 변환용 단일 견적 조회 (ID로 조회) + * 견적 상세페이지에서 수주등록 버튼 클릭 시 사용 + */ +export async function getQuoteByIdForSelect(id: string): Promise<{ + success: boolean; + data?: QuotationForSelect; + error?: string; + __authError?: boolean; +}> { + try { + const searchParams = new URLSearchParams(); + // 품목 포함 + searchParams.set('with_items', 'true'); + + const { response, error } = await serverFetch( + `${process.env.NEXT_PUBLIC_API_URL}/api/v1/quotes/${id}?${searchParams.toString()}`, + { method: 'GET', cache: 'no-store' } + ); + + if (error) { + return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' }; + } + + if (!response) { + return { success: false, error: '견적 조회에 실패했습니다.' }; + } + + const result: ApiResponse = await response.json(); + + if (!response.ok || !result.success) { + return { success: false, error: result.message || '견적 조회에 실패했습니다.' }; + } + + return { + success: true, + data: transformQuoteForSelect(result.data), + }; + } catch (error) { + console.error('[getQuoteByIdForSelect] Error:', error); + return { success: false, error: '서버 오류가 발생했습니다.' }; + } +} + /** * 수주 변환용 확정 견적 목록 조회 * QuotationSelectDialog에서 사용 diff --git a/src/components/orders/index.ts b/src/components/orders/index.ts index e881fc77..157f74a5 100644 --- a/src/components/orders/index.ts +++ b/src/components/orders/index.ts @@ -14,17 +14,20 @@ export { getOrderStats, revertProductionOrder, revertOrderConfirmation, + getQuoteByIdForSelect, type Order, type OrderItem as OrderItemApi, type OrderFormData as OrderApiFormData, type OrderItemFormData, type OrderStats, type OrderStatus, + type QuotationForSelect, + type QuotationItem, } from "./actions"; // Components export { OrderRegistration, type OrderFormData } from "./OrderRegistration"; -export { QuotationSelectDialog, type QuotationForSelect, type QuotationItem } from "./QuotationSelectDialog"; +export { QuotationSelectDialog } from "./QuotationSelectDialog"; export { ItemAddDialog, type OrderItem } from "./ItemAddDialog"; // 문서 컴포넌트 diff --git a/src/components/quotes/actions.ts b/src/components/quotes/actions.ts index 36cc4c69..039ba55e 100644 --- a/src/components/quotes/actions.ts +++ b/src/components/quotes/actions.ts @@ -1161,7 +1161,10 @@ export async function getItemCategoryTree(): Promise<{ } if (!response || !response.ok) { - return { success: false, data: [], error: '카테고리 조회 실패' }; + console.error('[getItemCategoryTree] response status:', response?.status, response?.statusText); + const errorBody = response ? await response.text().catch(() => '') : ''; + console.error('[getItemCategoryTree] response body:', errorBody); + return { success: false, data: [], error: `카테고리 조회 실패 (${response?.status || 'no response'})` }; } const result = await response.json();