From 4e967540e9fdd299cc7f10a7d6b8bd69b5280cbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Wed, 18 Mar 2026 15:28:48 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[order]=20=EC=88=98=EC=A3=BC=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20UI=20=EA=B0=9C=EC=84=A0=20-=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=9E=AC=EA=B5=AC=EC=84=B1,=20stats=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20STOCK=20=EC=A0=9C=ED=92=88=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수주관리 카드 재구성 (수주/생산/출하) - stats에 inProduction/produced 매핑 추가 - 수주관리 목록에서 재고생산(STOCK) 제품 필터링 --- .../sales/order-management-sales/page.tsx | 83 +++++++++++-------- src/components/orders/actions.ts | 4 + 2 files changed, 51 insertions(+), 36 deletions(-) diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/page.tsx index 2a38594f..ca3114fa 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/page.tsx @@ -4,7 +4,7 @@ * 수주관리 - IntegratedListTemplateV2 적용 * * 수주 관리 페이지 - * - 상단 통계 카드: 이번 달 수주, 분할 대기, 생산지시 대기, 출하 대기 + * - 상단 통계 카드: N월 수주(기간 수주), 수주, 생산, 출하 * - 상태 필터: 셀렉트박스 (전체, 수주등록, N자수정, 수주확정, 생산지시완료) * - 날짜 범위 필터: 달력 * - 완전한 반응형 지원 @@ -41,7 +41,7 @@ import { TableCell, } from "@/components/ui/table"; import { Checkbox } from "@/components/ui/checkbox"; -import { formatAmount, formatAmountManwon } from "@/lib/utils/amount"; +import { formatAmount } from "@/lib/utils/amount"; import { ListMobileCard, InfoField } from "@/components/organisms/MobileCard"; import { ConfirmDialog, DeleteConfirmDialog } from "@/components/ui/confirm-dialog"; import { @@ -131,9 +131,16 @@ function OrderListContent() { const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 20; - // 날짜 범위 필터 상태 - const [startDate, setStartDate] = useState(""); - const [endDate, setEndDate] = useState(""); + // 날짜 범위 필터 상태 (이번달 초기값) + const [startDate, setStartDate] = useState(() => { + const d = new Date(); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`; + }); + const [endDate, setEndDate] = useState(() => { + const d = new Date(); + const last = new Date(d.getFullYear(), d.getMonth() + 1, 0); + return `${last.getFullYear()}-${String(last.getMonth() + 1).padStart(2, '0')}-${String(last.getDate()).padStart(2, '0')}`; + }); // 필터 상태 const [filterValues, setFilterValues] = useState>({ @@ -181,7 +188,7 @@ function OrderListContent() { try { setIsLoading(true); const [ordersResult, statsResult] = await Promise.all([ - getOrders(), + getOrders({ order_type: 'ORDER' }), getOrderStats(), ]); @@ -276,53 +283,57 @@ function OrderListContent() { setMobileDisplayCount(20); }, [searchTerm, filterValues]); - // 통계 계산 (API stats 우선 사용, 없으면 로컬 계산) - const now = new Date(); - const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); + // 통계 계산 — 날짜 범위 내 데이터 기준 + const periodLabel = useMemo(() => { + if (!startDate || !endDate) return '전체 수주'; + const sm = startDate.slice(0, 7); // YYYY-MM + const em = endDate.slice(0, 7); + if (sm === em) { + return `${parseInt(startDate.slice(5, 7))}월 수주`; + } + return '기간 수주'; + }, [startDate, endDate]); - // 이번 달 수주 금액 (수주확정 이후 건만 - 취소, 수주등록 제외) - const thisMonthOrders = orders.filter( - (o) => - new Date(o.orderDate) >= startOfMonth && - o.status !== "cancelled" && - o.status !== "order_registered" - ); - const thisMonthAmount = apiStats?.thisMonthAmount ?? thisMonthOrders.reduce((sum, o) => sum + (Number(o.amount) || 0), 0); + // 날짜 범위 내 수주 건수 (취소 제외) + const periodOrderCount = orders.filter((o) => o.status !== "cancelled").length; - - // 분할 대기 (예시: 수주확정 상태) - const splitPendingCount = apiStats?.splitPending ?? orders.filter((o) => o.status === "order_confirmed").length; - - // 생산지시 대기 (수주확정 상태 중 생산지시 안된 것) - const productionPendingCount = apiStats?.productionPending ?? orders.filter( - (o) => o.status === "order_confirmed" || o.status === "order_registered" + // 수주: 생산에 넘어가지 않은 건 (DRAFT + CONFIRMED) + const orderCount = orders.filter( + (o) => o.status === "draft" || o.status === "order_registered" || o.status === "order_confirmed" ).length; - // 출하 대기 (작업완료 상태) - const shipPendingCount = apiStats?.shipPending ?? orders.filter((o) => o.status === "work_completed").length; + // 생산: 생산지시대기 + 생산중 (IN_PROGRESS + IN_PRODUCTION) + const productionCount = orders.filter( + (o) => o.status === "in_progress" || o.status === "in_production" + ).length; + + // 출하: 출하대기 ~ 출고중 (PRODUCED + SHIPPING) + const shipCount = orders.filter( + (o) => o.status === "produced" || o.status === "shipping" + ).length; const stats = [ { - label: "이번 달 수주", - value: formatAmountManwon(thisMonthAmount), + label: periodLabel, + value: `${periodOrderCount}건`, icon: DollarSign, iconColor: "text-blue-600", }, { - label: "분할 대기", - value: `${splitPendingCount}건`, - icon: SplitSquareVertical, + label: "수주", + value: `${orderCount}건`, + icon: ClipboardList, iconColor: "text-orange-600", }, { - label: "생산지시 대기", - value: `${productionPendingCount}건`, - icon: ClipboardList, + label: "생산", + value: `${productionCount}건`, + icon: SplitSquareVertical, iconColor: "text-green-600", }, { - label: "출하 대기", - value: `${shipPendingCount}건`, + label: "출하", + value: `${shipCount}건`, icon: Truck, iconColor: "text-purple-600", }, diff --git a/src/components/orders/actions.ts b/src/components/orders/actions.ts index c8064f46..917609c0 100644 --- a/src/components/orders/actions.ts +++ b/src/components/orders/actions.ts @@ -379,6 +379,8 @@ export interface OrderStats { draft: number; confirmed: number; inProgress: number; + inProduction: number; + produced: number; completed: number; cancelled: number; totalAmount: number; @@ -958,6 +960,8 @@ export async function getOrderStats(): Promise<{ draft: result.data.draft, confirmed: result.data.confirmed, inProgress: result.data.in_progress, + inProduction: result.data.in_production || 0, + produced: result.data.produced || 0, completed: result.data.completed, cancelled: result.data.cancelled, totalAmount: result.data.total_amount,