Files
sam-react-prod/src/components/orders/OrderSalesDetailView.tsx
유병철 a2c3e4c41e refactor(WEB): 프론트엔드 대규모 코드 정리 및 리팩토링
- 미사용 코드 삭제: ThemeContext, itemStore, utils/date.ts, utils/formatAmount.ts
- 유틸리티 이동: date, formatAmount → src/lib/utils/ (중앙 집중화)
- 다수 page.tsx 클라이언트 컴포넌트 패턴 통일
- DateRangeSelector 리팩토링 및 date-range-picker UI 컴포넌트 추가
- ThemeSelect/themeStore Zustand 직접 연동으로 전환
- 건설/회계/영업/품목/출하 등 전반적 컴포넌트 개선
- UniversalListPage, IntegratedListTemplateV2 타입 확장
- 프론트엔드 종합 리뷰 문서 및 개선 체크리스트 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 16:30:07 +09:00

824 lines
31 KiB
TypeScript

"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<OrderStatus, { label: string; className: string }> = {
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 (
<BadgeSm className={config.className}>
{config.label}
</BadgeSm>
);
}
// 정보 표시 컴포넌트
function InfoItem({ label, value }: { label: string; value: string }) {
return (
<div>
<p className="text-sm text-muted-foreground">{label}</p>
<p className="font-medium">{value || "-"}</p>
</div>
);
}
// 노드 상태 뱃지
const NODE_STATUS_CONFIG: Record<string, { label: string; className: string }> = {
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 (
<div className={`border rounded-lg ${depth > 0 ? 'ml-6' : ''}`}>
{/* 노드 헤더 */}
<button
type="button"
className="w-full flex items-center justify-between p-3 hover:bg-gray-50 transition-colors"
onClick={() => setIsOpen(!isOpen)}
>
<div className="flex items-center gap-3">
{isOpen ? (
<ChevronDown className="h-4 w-4 text-gray-500" />
) : (
<ChevronRight className="h-4 w-4 text-gray-500" />
)}
<MapPin className="h-4 w-4 text-blue-500" />
<span className="font-semibold text-sm">{node.name}</span>
{options.product_name ? (
<span className="text-xs text-muted-foreground">
({String(options.product_name)})
</span>
) : null}
{(options.open_width || options.open_height) ? (
<span className="text-xs text-muted-foreground">
{String(options.open_width ?? '')}x{String(options.open_height ?? '')}mm
</span>
) : null}
</div>
<div className="flex items-center gap-3">
<BadgeSm className={statusConfig.className}>
{statusConfig.label}
</BadgeSm>
<span className="text-sm font-medium">
{formatAmount(node.totalPrice)}
</span>
</div>
</button>
{/* 노드 내용 (접기/펼치기) */}
{isOpen && (
<div className="border-t">
{/* 해당 노드의 자재 테이블 */}
{node.items.length > 0 && (
<div className="overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[60px] text-center"></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-center"></TableHead>
<TableHead className="text-center"></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{node.items.map((item, index) => (
<TableRow key={item.id}>
<TableCell className="text-center">{index + 1}</TableCell>
<TableCell>
<code className="text-xs bg-gray-100 px-1.5 py-0.5 rounded">
{item.itemCode}
</code>
</TableCell>
<TableCell>{item.itemName}</TableCell>
<TableCell>{item.spec}</TableCell>
<TableCell className="text-center">{item.quantity}</TableCell>
<TableCell className="text-center">{item.unit}</TableCell>
<TableCell className="text-right">
{formatAmount(item.unitPrice)}
</TableCell>
<TableCell className="text-right font-medium">
{formatAmount(item.amount ?? 0)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
{/* 하위 노드 재귀 */}
{node.children.length > 0 && (
<div className="p-3 space-y-3">
{node.children.map((child) => (
<OrderNodeCard key={child.id} node={child} depth={depth + 1} />
))}
</div>
)}
</div>
)}
</div>
);
}
interface OrderSalesDetailViewProps {
orderId: string;
}
export function OrderSalesDetailView({ orderId }: OrderSalesDetailViewProps) {
const router = useRouter();
const [order, setOrder] = useState<Order | null>(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<OrderDocumentType>("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 && (
<Button onClick={handleConfirmOrder} className="bg-green-600 hover:bg-green-700" size="sm">
<CheckCircle2 className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"> </span>
</Button>
)}
{showProductionCreateButton && (
<Button onClick={handleProductionOrder} size="sm">
<Factory className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"> </span>
</Button>
)}
{showCancelButton && (
<Button variant="outline" onClick={handleCancel} className="border-orange-200 text-orange-600 hover:border-orange-300" size="sm">
<XCircle className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
)}
</>
);
}, [order, handleConfirmOrder, handleProductionOrder, handleCancel]);
// 폼 콘텐츠 렌더링
const renderFormContent = useCallback(() => {
if (!order) return null;
return (
<div className="space-y-6">
{/* 수주 정보 헤더 */}
<Card>
<CardHeader className="pb-4">
<div className="flex items-center justify-between flex-wrap gap-2">
<div className="flex items-center gap-3">
<code className="text-sm font-mono bg-gray-100 px-2 py-1 rounded">
{order.lotNumber}
</code>
{getOrderStatusBadge(order.status)}
</div>
<div className="text-sm text-muted-foreground">
: {order.orderDate}
</div>
</div>
</CardHeader>
<CardContent className="pt-0">
{/* 문서 버튼들 */}
<div className="flex items-center gap-2 pt-4 border-t">
<span className="text-sm text-muted-foreground mr-2">:</span>
<Button
variant="outline"
size="sm"
onClick={() => openDocumentModal("contract")}
>
<FileCheck className="h-4 w-4 mr-1" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => openDocumentModal("transaction")}
>
<FileSpreadsheet className="h-4 w-4 mr-1" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => openDocumentModal("purchaseOrder")}
>
<ClipboardList className="h-4 w-4 mr-1" />
</Button>
</div>
</CardContent>
</Card>
{/* 기본 정보 */}
<Card>
<CardHeader>
<CardTitle className="text-lg"> </CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
<InfoItem label="발주처" value={order.client} />
<InfoItem label="현장명" value={order.siteName} />
<InfoItem label="담당자" value={order.manager || ""} />
<InfoItem label="연락처" value={order.contact || ""} />
</div>
</CardContent>
</Card>
{/* 수주/배송 정보 */}
<Card>
<CardHeader>
<CardTitle className="text-lg">/ </CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
<InfoItem label="수주일자" value={order.orderDate} />
<InfoItem label="출고예정일" value={order.expectedShipDate || "미정"} />
<InfoItem label="납품요청일" value={order.deliveryRequestDate || ""} />
<InfoItem label="배송방식" value={order.deliveryMethodLabel || ""} />
<InfoItem label="운임비용" value={order.shippingCostLabel || ""} />
<InfoItem label="수신(반장/업체)" value={order.receiver || ""} />
<InfoItem label="수신처 연락처" value={order.receiverContact || ""} />
<InfoItem label="수신처 주소" value={order.address || ""} />
</div>
</CardContent>
</Card>
{/* 비고 */}
{order.remarks && (
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent>
<p className="whitespace-pre-wrap">{order.remarks}</p>
</CardContent>
</Card>
)}
{/* 제품 내역 */}
{order.nodes && order.nodes.length > 0 ? (
/* 노드별 그룹 표시 */
<Card>
<CardHeader>
<CardTitle className="text-lg flex items-center gap-2">
<MapPin className="h-5 w-5" />
({order.nodes.length})
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{order.nodes.map((node) => (
<OrderNodeCard key={node.id} node={node} />
))}
{/* 합계 */}
<div className="flex flex-col items-end gap-2 pt-4 mt-4 border-t">
<div className="flex items-center gap-4 text-sm">
<span className="text-muted-foreground">:</span>
<span className="w-32 text-right">
{formatAmount(order.subtotal ?? 0)}
</span>
</div>
<div className="flex items-center gap-4 text-sm">
<span className="text-muted-foreground">:</span>
<span className="w-32 text-right">{order.discountRate}%</span>
</div>
<div className="flex items-center gap-4 text-lg font-semibold">
<span>:</span>
<span className="w-32 text-right text-green-600">
{formatAmount(order.totalAmount ?? 0)}
</span>
</div>
</div>
</CardContent>
</Card>
) : (
/* 레거시 플랫 테이블 (노드 없는 기존 수주) */
<Card>
<CardHeader>
<CardTitle className="text-lg"> </CardTitle>
</CardHeader>
<CardContent>
<div className="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[60px] text-center"></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-center"></TableHead>
<TableHead className="text-center"></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{(order.items || []).map((item, index) => (
<TableRow key={item.id}>
<TableCell className="text-center">{index + 1}</TableCell>
<TableCell>
<code className="text-xs bg-gray-100 px-1.5 py-0.5 rounded">
{item.itemCode}
</code>
</TableCell>
<TableCell>{item.itemName}</TableCell>
<TableCell>{item.type || "-"}</TableCell>
<TableCell>{item.symbol || "-"}</TableCell>
<TableCell>{item.spec}</TableCell>
<TableCell className="text-center">{item.quantity}</TableCell>
<TableCell className="text-center">{item.unit}</TableCell>
<TableCell className="text-right">
{formatAmount(item.unitPrice)}
</TableCell>
<TableCell className="text-right font-medium">
{formatAmount(item.amount ?? 0)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
{/* 합계 */}
<div className="flex flex-col items-end gap-2 pt-4 mt-4 border-t">
<div className="flex items-center gap-4 text-sm">
<span className="text-muted-foreground">:</span>
<span className="w-32 text-right">
{formatAmount(order.subtotal ?? 0)}
</span>
</div>
<div className="flex items-center gap-4 text-sm">
<span className="text-muted-foreground">:</span>
<span className="w-32 text-right">{order.discountRate}%</span>
</div>
<div className="flex items-center gap-4 text-lg font-semibold">
<span>:</span>
<span className="w-32 text-right text-green-600">
{formatAmount(order.totalAmount ?? 0)}
</span>
</div>
</div>
</CardContent>
</Card>
)}
</div>
);
}, [order, openDocumentModal]);
// 에러 상태
if (!loading && !order) {
return (
<ServerErrorPage
title="수주 정보를 불러올 수 없습니다"
message="수주 정보를 찾을 수 없습니다."
showBackButton={true}
showHomeButton={true}
/>
);
}
return (
<>
<IntegratedDetailTemplate
config={dynamicConfig}
mode="view"
initialData={order || {}}
itemId={orderId}
isLoading={loading}
headerActions={customHeaderActions}
renderView={() => renderFormContent()}
renderForm={() => renderFormContent()}
/>
{/* 문서 모달 */}
{order && (
<OrderDocumentModal
open={documentModalOpen}
onOpenChange={setDocumentModalOpen}
documentType={documentType}
data={{
lotNumber: order.lotNumber,
orderDate: order.orderDate,
client: order.client,
siteName: order.siteName,
manager: order.manager,
managerContact: order.contact,
deliveryRequestDate: order.deliveryRequestDate,
expectedShipDate: order.expectedShipDate,
deliveryMethod: order.deliveryMethodLabel,
address: order.address,
items: order.items,
subtotal: order.subtotal,
discountRate: order.discountRate,
totalAmount: order.totalAmount,
remarks: order.remarks,
}}
/>
)}
{/* 취소 확인 다이얼로그 */}
{order && (
<Dialog open={isCancelDialogOpen} onOpenChange={setIsCancelDialogOpen}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<XCircle className="h-5 w-5" />
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
{/* 수주 정보 박스 */}
<div className="border rounded-lg p-4 space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground"></span>
<span className="font-medium">{order.lotNumber}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground"></span>
<span>{order.client}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground"></span>
<span>{order.siteName}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-muted-foreground"> </span>
{getOrderStatusBadge(order.status)}
</div>
</div>
{/* 취소 사유 선택 */}
<div className="space-y-2">
<Label htmlFor="cancelReason">
<span className="text-red-500">*</span>
</Label>
<Select value={cancelReason} onValueChange={setCancelReason}>
<SelectTrigger id="cancelReason">
<SelectValue placeholder="취소 사유를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="customer_request"> </SelectItem>
<SelectItem value="spec_change"> </SelectItem>
<SelectItem value="price_issue"> </SelectItem>
<SelectItem value="delivery_issue"> </SelectItem>
<SelectItem value="duplicate_order"> </SelectItem>
<SelectItem value="other"></SelectItem>
</SelectContent>
</Select>
</div>
{/* 상세 사유 입력 */}
<div className="space-y-2">
<Label htmlFor="cancelDetail"> </Label>
<Textarea
id="cancelDetail"
placeholder="취소 사유에 대한 상세 내용을 입력하세요"
value={cancelDetail}
onChange={(e) => setCancelDetail(e.target.value)}
rows={3}
/>
</div>
{/* 취소 시 유의사항 */}
<div className="bg-gray-50 border rounded-lg p-4 text-sm space-y-1">
<p className="font-medium mb-2"> </p>
<ul className="space-y-1 text-muted-foreground">
<li> '취소' </li>
<li> </li>
<li> </li>
</ul>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setIsCancelDialogOpen(false)}
>
</Button>
<Button
variant="outline"
onClick={handleConfirmCancel}
className="border-gray-300"
disabled={isCancelling}
>
<XCircle className="h-4 w-4 mr-1" />
{isCancelling ? "취소 중..." : "취소 확정"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}
{/* 수주 확정 다이얼로그 */}
{order && (
<Dialog open={isConfirmDialogOpen} onOpenChange={setIsConfirmDialogOpen}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<CheckCircle2 className="h-5 w-5 text-green-600" />
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
{/* 수주 정보 박스 */}
<div className="border rounded-lg p-4 space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground"></span>
<span className="font-medium">{order.lotNumber}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground"></span>
<span>{order.client}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground"></span>
<span>{order.siteName}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground"></span>
<span className="font-medium text-green-600">
{formatAmount(order.totalAmount ?? 0)}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-muted-foreground"> </span>
{getOrderStatusBadge(order.status)}
</div>
</div>
{/* 확정 안내 */}
<div className="bg-green-50 border border-green-200 rounded-lg p-4 text-sm space-y-1">
<p className="font-medium mb-2 text-green-700"> </p>
<ul className="space-y-1 text-green-600">
<li> '수주확정' </li>
<li> </li>
<li> </li>
</ul>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setIsConfirmDialogOpen(false)}
>
</Button>
<Button
onClick={handleConfirmOrderSubmit}
className="bg-green-600 hover:bg-green-700"
disabled={isConfirming}
>
<CheckCircle2 className="h-4 w-4 mr-1" />
{isConfirming ? "확정 중..." : "확정"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}
</>
);
}