"use client"; /** * 수주 상세 보기 컴포넌트 (View Mode) * IntegratedDetailTemplate 마이그레이션 완료 (2026-01-20) * * - 문서 모달: 계약서, 거래명세서, 발주서 * - 기본 정보, 수주/배송 정보, 비고 * - 제품 내역 테이블 * - 상태별 버튼 차이 */ import { useState, useEffect, useMemo, useCallback } from "react"; import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { FileText, Factory, XCircle, FileSpreadsheet, FileCheck, ClipboardList, CheckCircle2, ChevronDown, ChevronRight, MapPin, } from "lucide-react"; import { toast } from "sonner"; import { IntegratedDetailTemplate } from "@/components/templates/IntegratedDetailTemplate"; import { orderSalesConfig } from "./orderSalesConfig"; import { BadgeSm } from "@/components/atoms/BadgeSm"; import { formatAmount } from "@/lib/utils/amount"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { OrderDocumentModal, type OrderDocumentType, getOrderById, updateOrderStatus, type Order, type OrderNode, type OrderStatus, } from "@/components/orders"; import { ServerErrorPage } from "@/components/common/ServerErrorPage"; // 상태 뱃지 헬퍼 function getOrderStatusBadge(status: OrderStatus) { const statusConfig: Record = { order_registered: { label: "수주등록", className: "bg-gray-100 text-gray-700 border-gray-200" }, order_confirmed: { label: "수주확정", className: "bg-gray-100 text-gray-700 border-gray-200" }, production_ordered: { label: "생산지시완료", className: "bg-blue-100 text-blue-700 border-blue-200" }, in_production: { label: "생산중", className: "bg-green-100 text-green-700 border-green-200" }, produced: { label: "생산완료", className: "bg-blue-100 text-blue-700 border-blue-200" }, rework: { label: "재작업중", className: "bg-orange-100 text-orange-700 border-orange-200" }, work_completed: { label: "작업완료", className: "bg-blue-600 text-white border-blue-600" }, shipping: { label: "출하중", className: "bg-purple-100 text-purple-700 border-purple-200" }, shipped: { label: "출하완료", className: "bg-gray-500 text-white border-gray-500" }, completed: { label: "완료", className: "bg-gray-500 text-white border-gray-500" }, cancelled: { label: "취소", className: "bg-red-100 text-red-700 border-red-200" }, }; const config = statusConfig[status] || { label: status, className: "bg-gray-100 text-gray-700 border-gray-200" }; return ( {config.label} ); } // 정보 표시 컴포넌트 function InfoItem({ label, value }: { label: string; value: string }) { return (

{label}

{value || "-"}

); } // 노드 상태 뱃지 const NODE_STATUS_CONFIG: Record = { PENDING: { label: "대기", className: "bg-gray-100 text-gray-700 border-gray-200" }, CONFIRMED: { label: "확정", className: "bg-blue-100 text-blue-700 border-blue-200" }, IN_PRODUCTION: { label: "생산중", className: "bg-green-100 text-green-700 border-green-200" }, PRODUCED: { label: "생산완료", className: "bg-blue-600 text-white border-blue-600" }, SHIPPED: { label: "출하완료", className: "bg-gray-500 text-white border-gray-500" }, COMPLETED: { label: "완료", className: "bg-gray-500 text-white border-gray-500" }, CANCELLED: { label: "취소", className: "bg-red-100 text-red-700 border-red-200" }, }; // 노드별 카드 컴포넌트 (재귀 지원) function OrderNodeCard({ node, depth = 0 }: { node: OrderNode; depth?: number }) { const [isOpen, setIsOpen] = useState(true); const statusConfig = NODE_STATUS_CONFIG[node.statusCode] || NODE_STATUS_CONFIG.PENDING; const options = node.options || {}; return (
0 ? 'ml-6' : ''}`}> {/* 노드 헤더 */} {/* 노드 내용 (접기/펼치기) */} {isOpen && (
{/* 해당 노드의 자재 테이블 */} {node.items.length > 0 && (
순번 품목코드 품명 규격 수량 단위 단가 금액 {node.items.map((item, index) => ( {index + 1} {item.itemCode} {item.itemName} {item.spec} {item.quantity} {item.unit} {formatAmount(item.unitPrice)}원 {formatAmount(item.amount ?? 0)}원 ))}
)} {/* 하위 노드 재귀 */} {node.children.length > 0 && (
{node.children.map((child) => ( ))}
)}
)}
); } interface OrderSalesDetailViewProps { orderId: string; } export function OrderSalesDetailView({ orderId }: OrderSalesDetailViewProps) { const router = useRouter(); const [order, setOrder] = useState(null); const [loading, setLoading] = useState(true); const [isCancelDialogOpen, setIsCancelDialogOpen] = useState(false); const [isCancelling, setIsCancelling] = useState(false); const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false); const [isConfirming, setIsConfirming] = useState(false); // 취소 폼 상태 const [cancelReason, setCancelReason] = useState(""); const [cancelDetail, setCancelDetail] = useState(""); // 문서 모달 상태 const [documentModalOpen, setDocumentModalOpen] = useState(false); const [documentType, setDocumentType] = useState("contract"); // 데이터 로드 (API 호출) useEffect(() => { async function loadOrder() { try { setLoading(true); const result = await getOrderById(orderId); if (result.success && result.data) { setOrder(result.data); } else { toast.error(result.error || "수주 정보를 불러오는데 실패했습니다."); setOrder(null); } } catch (error) { console.error("Error loading order:", error); toast.error("수주 정보를 불러오는 중 오류가 발생했습니다."); setOrder(null); } finally { setLoading(false); } } loadOrder(); }, [orderId]); const handleBack = () => { router.push("/sales/order-management-sales"); }; const handleEdit = () => { // V2 패턴: ?mode=edit로 이동 router.push(`/sales/order-management-sales/${orderId}?mode=edit`); }; const handleProductionOrder = () => { // 생산지시 생성 페이지로 이동 router.push(`/sales/order-management-sales/${orderId}/production-order`); }; const handleViewProductionOrder = () => { // 생산지시 목록 페이지로 이동 (수주관리 내부) router.push(`/sales/order-management-sales/production-orders`); }; const handleCancel = () => { setCancelReason(""); setCancelDetail(""); setIsCancelDialogOpen(true); }; const handleConfirmCancel = async () => { if (!cancelReason) { toast.error("취소 사유를 선택해주세요."); return; } if (order) { setIsCancelling(true); try { const result = await updateOrderStatus(order.id, "cancelled"); if (result.success) { setOrder({ ...order, status: "cancelled" }); toast.success("수주가 취소되었습니다."); setIsCancelDialogOpen(false); setCancelReason(""); setCancelDetail(""); } else { toast.error(result.error || "수주 취소에 실패했습니다."); } } catch (error) { console.error("Error cancelling order:", error); toast.error("수주 취소 중 오류가 발생했습니다."); } finally { setIsCancelling(false); } } }; // 수주 확정 처리 const handleConfirmOrder = () => { setIsConfirmDialogOpen(true); }; const handleConfirmOrderSubmit = async () => { if (order) { setIsConfirming(true); try { const result = await updateOrderStatus(order.id, "order_confirmed"); if (result.success && result.data) { setOrder(result.data); toast.success("수주가 확정되었습니다."); setIsConfirmDialogOpen(false); } else { toast.error(result.error || "수주 확정에 실패했습니다."); } } catch (error) { console.error("Error confirming order:", error); toast.error("수주 확정 중 오류가 발생했습니다."); } finally { setIsConfirming(false); } } }; // 문서 모달 열기 const openDocumentModal = useCallback((type: OrderDocumentType) => { setDocumentType(type); setDocumentModalOpen(true); }, []); // 동적 config (상태별 수정 버튼 표시) const dynamicConfig = useMemo(() => { const canEdit = order?.status !== "shipped" && order?.status !== "cancelled"; return { ...orderSalesConfig, actions: { ...orderSalesConfig.actions, showEdit: canEdit, }, }; }, [order?.status]); // 커스텀 헤더 액션 (상태별 버튼들) const customHeaderActions = useMemo(() => { if (!order) return null; const showConfirmButton = order.status === "order_registered"; const showProductionCreateButton = order.status !== "shipped" && order.status !== "cancelled" && order.status !== "production_ordered"; const showCancelButton = order.status !== "shipped" && order.status !== "cancelled" && order.status !== "production_ordered"; return ( <> {showConfirmButton && ( )} {showProductionCreateButton && ( )} {showCancelButton && ( )} ); }, [order, handleConfirmOrder, handleProductionOrder, handleCancel]); // 폼 콘텐츠 렌더링 const renderFormContent = useCallback(() => { if (!order) return null; return (
{/* 수주 정보 헤더 */}
{order.lotNumber} {getOrderStatusBadge(order.status)}
수주일: {order.orderDate}
{/* 문서 버튼들 */}
문서:
{/* 기본 정보 */} 기본 정보
{/* 수주/배송 정보 */} 수주/배송 정보
{/* 비고 */} {order.remarks && ( 비고

{order.remarks}

)} {/* 제품 내역 */} {order.nodes && order.nodes.length > 0 ? ( /* 노드별 그룹 표시 */ 개소별 내역 ({order.nodes.length}개소) {order.nodes.map((node) => ( ))} {/* 합계 */}
소계: {formatAmount(order.subtotal ?? 0)}원
할인율: {order.discountRate}%
총금액: {formatAmount(order.totalAmount ?? 0)}원
) : ( /* 레거시 플랫 테이블 (노드 없는 기존 수주) */ 제품 내역
순번 품목코드 품명 부호 규격 수량 단위 단가 금액 {(order.items || []).map((item, index) => ( {index + 1} {item.itemCode} {item.itemName} {item.type || "-"} {item.symbol || "-"} {item.spec} {item.quantity} {item.unit} {formatAmount(item.unitPrice)}원 {formatAmount(item.amount ?? 0)}원 ))}
{/* 합계 */}
소계: {formatAmount(order.subtotal ?? 0)}원
할인율: {order.discountRate}%
총금액: {formatAmount(order.totalAmount ?? 0)}원
)}
); }, [order, openDocumentModal]); // 에러 상태 if (!loading && !order) { return ( ); } return ( <> renderFormContent()} renderForm={() => renderFormContent()} /> {/* 문서 모달 */} {order && ( )} {/* 취소 확인 다이얼로그 */} {order && ( 수주 취소
{/* 수주 정보 박스 */}
수주번호 {order.lotNumber}
발주처 {order.client}
현장명 {order.siteName}
현재 상태 {getOrderStatusBadge(order.status)}
{/* 취소 사유 선택 */}
{/* 상세 사유 입력 */}