fix: [order] 수주관리 UI 개선 - 카드 재구성, stats 상태 추가, STOCK 제품 필터링
- 수주관리 카드 재구성 (수주/생산/출하) - stats에 inProduction/produced 매핑 추가 - 수주관리 목록에서 재고생산(STOCK) 제품 필터링
This commit is contained in:
@@ -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<string>("");
|
||||
const [endDate, setEndDate] = useState<string>("");
|
||||
// 날짜 범위 필터 상태 (이번달 초기값)
|
||||
const [startDate, setStartDate] = useState<string>(() => {
|
||||
const d = new Date();
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;
|
||||
});
|
||||
const [endDate, setEndDate] = useState<string>(() => {
|
||||
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<Record<string, string | string[]>>({
|
||||
@@ -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",
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user