From 09b2c256fb4586076a25fa73e94995d0f31c54ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EB=B3=91=EC=B2=A0?= Date: Tue, 20 Jan 2026 16:47:33 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20IntegratedDetailTemplate=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20(?= =?UTF-8?q?=EC=88=98=EC=A3=BC/=EA=B2=AC=EC=A0=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - order-management-sales/[id]/page.tsx: PageLayout → IntegratedDetailTemplate (view) - order-management-sales/[id]/edit/page.tsx: PageLayout → IntegratedDetailTemplate (edit) - EstimateDetailForm.tsx: PageLayout → IntegratedDetailTemplate (view/edit) Co-Authored-By: Claude Opus 4.5 --- .../order-management-sales/[id]/edit/page.tsx | 101 +++++---- .../order-management-sales/[id]/page.tsx | 191 ++++++++---------- .../estimates/EstimateDetailForm.tsx | 111 ++++++---- 3 files changed, 205 insertions(+), 198 deletions(-) diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/edit/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/edit/page.tsx index 0fdf3cfe..de835576 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/edit/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/edit/page.tsx @@ -9,7 +9,7 @@ * - 품목 내역 (생산 시작 후 수정 불가) */ -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { useRouter, useParams } from "next/navigation"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; @@ -31,11 +31,10 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import { FileText, AlertTriangle } from "lucide-react"; +import { AlertTriangle } from "lucide-react"; import { toast } from "sonner"; -import { PageLayout } from "@/components/organisms/PageLayout"; -import { PageHeader } from "@/components/organisms/PageHeader"; -import { FormActions } from "@/components/organisms/FormActions"; +import { IntegratedDetailTemplate } from "@/components/templates/IntegratedDetailTemplate"; +import { orderSalesConfig } from "@/components/orders/orderSalesConfig"; import { BadgeSm } from "@/components/atoms/BadgeSm"; import { formatAmount } from "@/utils/formatAmount"; import { @@ -119,7 +118,6 @@ export default function OrderEditPage() { const [form, setForm] = useState(null); const [loading, setLoading] = useState(true); - const [isSaving, setIsSaving] = useState(false); // 데이터 로드 (API) useEffect(() => { @@ -177,28 +175,25 @@ export default function OrderEditPage() { router.push(`/sales/order-management-sales/${orderId}`); }; - const handleSave = async () => { - if (!form) return; + // onSubmit wrapper for IntegratedDetailTemplate + const handleSubmit = useCallback(async (): Promise<{ success: boolean; error?: string }> => { + if (!form) return { success: false, error: "폼 데이터가 없습니다." }; // 유효성 검사 if (!form.deliveryRequestDate) { toast.error("납품요청일을 입력해주세요."); - return; + return { success: false, error: "납품요청일을 입력해주세요." }; } if (!form.receiver.trim()) { toast.error("수신(반장/업체)을 입력해주세요."); - return; + return { success: false, error: "수신(반장/업체)을 입력해주세요." }; } if (!form.receiverContact.trim()) { toast.error("수신처 연락처를 입력해주세요."); - return; + return { success: false, error: "수신처 연락처를 입력해주세요." }; } - setIsSaving(true); try { - // API 연동 - // 주의: clientId를 보내지 않으면 기존 값 유지됨 - // clientName과 clientContact는 반드시 보내야 기존 값이 유지됨 const result = await updateOrder(orderId, { clientName: form.client, clientContact: form.contact, @@ -226,44 +221,32 @@ export default function OrderEditPage() { if (result.success) { toast.success("수주가 수정되었습니다."); router.push(`/sales/order-management-sales/${orderId}`); + return { success: true }; } else { toast.error(result.error || "수주 수정에 실패했습니다."); + return { success: false, error: result.error }; } } catch (error) { console.error("Error updating order:", error); toast.error("수주 수정 중 오류가 발생했습니다."); - } finally { - setIsSaving(false); + return { success: false, error: "수주 수정 중 오류가 발생했습니다." }; } - }; + }, [form, orderId, router]); + + // 폼 컨텐츠 렌더링 + const renderFormContent = useCallback(() => { + if (!form) return null; - if (loading || !form) { return ( - -
-
-
- - ); - } - - return ( - - {/* 헤더 */} - - - {form.lotNumber} - - {getOrderStatusBadge(form.status)} -
- } - /> -
+ {/* 상태 뱃지 */} +
+ + {form.lotNumber} + + {getOrderStatusBadge(form.status)} +
+ {/* 기본 정보 (읽기전용) */} @@ -545,18 +528,26 @@ export default function OrderEditPage() {
+ ); + }, [form]); - {/* 하단 버튼 */} -
- -
-
+ // Edit mode config override + const editConfig = { + ...orderSalesConfig, + title: '수주 수정', + }; + + return ( + ); } \ No newline at end of file diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/page.tsx index c926177b..b680f045 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/page.tsx @@ -9,7 +9,7 @@ * - 상태별 버튼 차이 */ -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { useRouter, useParams } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; @@ -22,8 +22,6 @@ import { TableRow, } from "@/components/ui/table"; import { - FileText, - ArrowLeft, Edit, Factory, XCircle, @@ -39,8 +37,8 @@ import { ChevronsUpDown, } from "lucide-react"; import { toast } from "sonner"; -import { PageLayout } from "@/components/organisms/PageLayout"; -import { PageHeader } from "@/components/organisms/PageHeader"; +import { IntegratedDetailTemplate } from "@/components/templates/IntegratedDetailTemplate"; +import { orderSalesConfig } from "@/components/orders/orderSalesConfig"; import { BadgeSm } from "@/components/atoms/BadgeSm"; import { formatAmount } from "@/utils/formatAmount"; import { @@ -339,105 +337,11 @@ export default function OrderDetailPage() { return order.items.filter((item) => !matchedIds.has(item.id)); }; - if (loading) { + // 상세 컨텐츠 렌더링 + const renderViewContent = useCallback(() => { + if (!order) return null; + return ( - -
-
-
- - ); - } - - if (!order) { - return ( - -
-

수주 정보를 찾을 수 없습니다.

- -
-
- ); - } - - // 상태별 버튼 표시 여부 - const showEditButton = order.status !== "shipped" && order.status !== "cancelled"; - // 수주 확정 버튼: 수주등록 상태에서만 표시 - const showConfirmButton = order.status === "order_registered"; - // 생산지시 생성 버튼: 수주확정 상태에서만 표시 - const showProductionCreateButton = order.status === "order_confirmed"; - // 생산지시 보기 버튼: 생산지시완료 상태에서 숨김 (기획서 오류로 제거) - const showProductionViewButton = false; - // 생산지시 되돌리기 버튼: 생산지시완료 상태에서만 표시 - const showRevertButton = order.status === "production_ordered"; - // 수주확정 되돌리기 버튼: 수주확정 상태에서만 표시 - const showRevertConfirmButton = order.status === "order_confirmed"; - const showCancelButton = - order.status !== "shipped" && - order.status !== "cancelled" && - order.status !== "production_ordered"; - - return ( - - {/* 헤더 */} - - - {showEditButton && ( - - )} - {showConfirmButton && ( - - )} - {showProductionCreateButton && ( - - )} - {showProductionViewButton && ( - - )} - {showRevertButton && ( - - )} - {showRevertConfirmButton && ( - - )} - {showCancelButton && ( - - )} -
- } - /> -
{/* 수주 정보 헤더 */} @@ -790,6 +694,85 @@ export default function OrderDetailPage() {
+ ); + }, [order, expandedProducts, expandAllProducts, collapseAllProducts, toggleProduct, getItemsForProduct, getUnmatchedItems, openDocumentModal]); + + // 커스텀 헤더 액션 (상태별 버튼) + const renderHeaderActions = useCallback(() => { + if (!order) return null; + + // 상태별 버튼 표시 여부 + const showEditButton = order.status !== "shipped" && order.status !== "cancelled"; + const showConfirmButton = order.status === "order_registered"; + const showProductionCreateButton = order.status === "order_confirmed"; + const showProductionViewButton = false; + const showRevertButton = order.status === "production_ordered"; + const showRevertConfirmButton = order.status === "order_confirmed"; + const showCancelButton = + order.status !== "shipped" && + order.status !== "cancelled" && + order.status !== "production_ordered"; + + return ( +
+ {showEditButton && ( + + )} + {showConfirmButton && ( + + )} + {showProductionCreateButton && ( + + )} + {showProductionViewButton && ( + + )} + {showRevertButton && ( + + )} + {showRevertConfirmButton && ( + + )} + {showCancelButton && ( + + )} +
+ ); + }, [order, handleEdit, handleConfirmOrder, handleProductionOrder, handleViewProductionOrder, handleRevertProduction, handleRevertConfirmation, handleCancel]); + + return ( + <> + {/* 문서 모달 */} -
+ ); } \ No newline at end of file diff --git a/src/components/business/construction/estimates/EstimateDetailForm.tsx b/src/components/business/construction/estimates/EstimateDetailForm.tsx index a90ec122..71a645ad 100644 --- a/src/components/business/construction/estimates/EstimateDetailForm.tsx +++ b/src/components/business/construction/estimates/EstimateDetailForm.tsx @@ -2,7 +2,7 @@ import { useState, useCallback, useMemo, useRef, useEffect } from 'react'; import { useRouter } from 'next/navigation'; -import { FileText, Loader2, List } from 'lucide-react'; +import { Loader2 } from 'lucide-react'; import { getExpenseItemOptions, updateEstimate, type ExpenseItemOption } from './actions'; import { createBiddingFromEstimate } from '../bidding/actions'; import { useAuth } from '@/contexts/AuthContext'; @@ -17,8 +17,8 @@ import { AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; -import { PageLayout } from '@/components/organisms/PageLayout'; -import { PageHeader } from '@/components/organisms/PageHeader'; +import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; +import { estimateConfig } from './estimateConfig'; import { toast } from 'sonner'; import type { EstimateDetail, @@ -106,9 +106,6 @@ export default function EstimateDetailForm({ controller: number; } | null>(null); - // 조정단가 적용 여부 (전체 적용 버튼 클릭 시에만 true) - const useAdjustedPrice = appliedPrices !== null; - // ===== 네비게이션 핸들러 ===== const handleBack = useCallback(() => { router.push('/ko/construction/project/bidding/estimates'); @@ -118,10 +115,6 @@ export default function EstimateDetailForm({ router.push(`/ko/construction/project/bidding/estimates/${estimateId}/edit`); }, [router, estimateId]); - const handleCancel = useCallback(() => { - router.push(`/ko/construction/project/bidding/estimates/${estimateId}`); - }, [router, estimateId]); - // ===== 저장/삭제 핸들러 ===== const handleSave = useCallback(() => { setShowSaveDialog(true); @@ -622,17 +615,8 @@ export default function EstimateDetailForm({ [isViewMode] ); - // ===== 타이틀 및 설명 ===== - const pageTitle = useMemo(() => { - return isEditMode ? '견적 수정' : '견적 상세'; - }, [isEditMode]); - - const pageDescription = useMemo(() => { - return isEditMode ? '견적 정보를 수정합니다' : '견적 정보를 등록하고 관리합니다'; - }, [isEditMode]); - // ===== 헤더 버튼 ===== - const headerActions = useMemo(() => { + const renderHeaderActions = useCallback(() => { if (isViewMode) { return (
@@ -645,18 +629,11 @@ export default function EstimateDetailForm({ -
); } return (
-
); - }, [isViewMode, isLoading, handleBack, handleEdit, handleDelete, handleSave, handleRegisterBidding]); - - return ( - - + }, [isViewMode, isLoading, handleDelete, handleSave, handleRegisterBidding]); + // ===== 컨텐츠 렌더링 ===== + const renderContent = useCallback(() => { + return (
{/* 견적 정보 + 현장설명회 + 입찰 정보 */}
+ ); + }, [ + formData, + isViewMode, + isDragging, + documentInputRef, + expenseOptions, + appliedPrices, + handleBidInfoChange, + handleDocumentUpload, + handleDocumentRemove, + handleDragOver, + handleDragLeave, + handleDrop, + handleAddSummaryItem, + handleRemoveSummaryItem, + handleSummaryItemChange, + handleSummaryMemoChange, + handleAddExpenseItems, + handleRemoveSelectedExpenseItems, + handleExpenseItemChange, + handleExpenseSelectItem, + handleExpenseSelectAll, + handlePriceAdjustmentChange, + handlePriceAdjustmentSave, + handlePriceAdjustmentApplyAll, + handlePriceAdjustmentReset, + handleAddDetailItems, + handleRemoveDetailItem, + handleRemoveSelectedDetailItems, + handleDetailItemChange, + handleDetailSelectItem, + handleDetailSelectAll, + handleApplyAdjustedPriceToSelected, + handleDetailReset, + ]); + + // Edit 모드용 config (타이틀 변경) + const currentConfig = useMemo(() => { + if (isEditMode) { + return { + ...estimateConfig, + title: '견적 수정', + description: '견적 정보를 수정합니다', + }; + } + return estimateConfig; + }, [isEditMode]); + + return ( + <> + {/* 전자결재 모달 */} -
+ ); }