From a1208037991354aca079250453bdb57545f87734 Mon Sep 17 00:00:00 2001 From: chpark Date: Fri, 15 May 2026 01:16:56 +0900 Subject: [PATCH] =?UTF-8?q?fix(inventory):=20=EC=9E=AC=EA=B3=A0=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EB=A7=A4=ED=8A=B8=EB=A6=AD=EC=8A=A4=20=E2=80=94=20?= =?UTF-8?q?=ED=92=88=EB=AA=A9=20=EA=B0=80=EB=A1=9C(=EA=B8=B0=EB=B3=B8)=20/?= =?UTF-8?q?=20=EC=B0=BD=EA=B3=A0=20=EA=B0=80=EB=A1=9C=20=ED=86=A0=EA=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(main)/m/admin/inventory/page.tsx | 167 ++++++++++++++++------ 1 file changed, 127 insertions(+), 40 deletions(-) diff --git a/src/app/(main)/m/admin/inventory/page.tsx b/src/app/(main)/m/admin/inventory/page.tsx index fe736de..e83ea70 100644 --- a/src/app/(main)/m/admin/inventory/page.tsx +++ b/src/app/(main)/m/admin/inventory/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; -import { Plus, Search, Trash2, History, ArrowRightLeft, Package } from "lucide-react"; +import { Plus, Search, Trash2, History, ArrowRightLeft, Package, LayoutGrid, Columns3 } from "lucide-react"; import { useRouter } from "next/navigation"; import Swal from "sweetalert2"; @@ -35,6 +35,29 @@ export default function InventoryPage() { const [trItem, setTrItem] = useState(""); const [trQty, setTrQty] = useState(1); + // 매트릭스 보기 모드 — 기본 '품목 가로' (헤더=품목, 행=창고). 토글로 '창고 가로'. + const [viewMode, setViewMode] = useState<"by-item" | "by-wh">("by-item"); + + // list 평면 → 매트릭스 pivot + const matrix = useMemo(() => { + const itemSet = new Map(); + const whSet = new Map(); + const cell: Record> = {}; + // cell[itemObjid][whObjid] = { qty, updateDate } + for (const s of list) { + if (!itemSet.has(s.ITEM_OBJID)) itemSet.set(s.ITEM_OBJID, { + OBJID: s.ITEM_OBJID, CODE: s.ITEM_CODE, NAME: s.ITEM_NAME, UNIT: s.UNIT, IS_TAX_FREE: s.IS_TAX_FREE, + }); + if (!whSet.has(s.WH_OBJID)) whSet.set(s.WH_OBJID, { OBJID: s.WH_OBJID, CODE: s.WH_CODE, NAME: s.WH_NAME }); + if (!cell[s.ITEM_OBJID]) cell[s.ITEM_OBJID] = {}; + cell[s.ITEM_OBJID][s.WH_OBJID] = { qty: Number(s.QTY), updateDate: s.UPDATE_DATE }; + } + // 창고 7개 — list 에 없는 창고도 헤더에 포함시키려면 whs 사용 + const allWhs = [...whs].sort((a, b) => a.WH_CODE.localeCompare(b.WH_CODE)); + const itemList = Array.from(itemSet.values()).sort((a, b) => a.NAME.localeCompare(b.NAME)); + return { items: itemList, warehouses: allWhs, cell }; + }, [list, whs]); + const load = async () => { const res = await fetch("/api/m/inventory/list", { method: "POST", headers: { "Content-Type": "application/json" }, @@ -158,46 +181,110 @@ export default function InventoryPage() { - {/* 데스크톱 테이블 */} -
-
- - - - - - - - - - - - - - {list.length === 0 ? ( - - ) : list.map((s) => ( - - - - - - - - + {/* 데스크톱 — 매트릭스 토글 (품목 가로 ↔ 창고 가로) */} +
+
+
+ + +
+
+
+ {matrix.items.length === 0 ? ( +
재고 데이터가 없습니다. 매입 입고로 등록하세요.
+ ) : viewMode === "by-item" ? ( + /* 품목 가로 (기본): 헤더=품목, 행=창고 */ +
창고품목코드품목명구분현재고최종 변경재고이력
재고 데이터가 없습니다. 매입 입고로 등록하세요.
{s.WH_NAME}{s.ITEM_CODE}{s.ITEM_NAME} - {s.IS_TAX_FREE === "Y" - ? 면세 - : 과세} - {fmt(s.QTY)} {s.UNIT}{s.UPDATE_DATE} - -
+ + + + {matrix.items.map((it) => ( + + ))} - ))} - -
창고 +
{it.NAME}
+
{it.CODE}
+
+ + + {matrix.warehouses.map((w) => ( + + + {w.WH_NAME} +
{w.WH_CODE}
+ + {matrix.items.map((it) => { + const c = matrix.cell[it.OBJID]?.[w.OBJID]; + const qty = c ? c.qty : 0; + return ( + + {qty === 0 ? "-" : ( + + )} + + ); + })} + + ))} + + + ) : ( + /* 창고 가로: 헤더=창고, 행=품목 */ + + + + + {matrix.warehouses.map((w) => ( + + ))} + + + + {matrix.items.map((it) => ( + + + {matrix.warehouses.map((w) => { + const c = matrix.cell[it.OBJID]?.[w.OBJID]; + const qty = c ? c.qty : 0; + return ( + + ); + })} + + ))} + +
품목 + {w.WH_NAME} +
+ {it.NAME} + {it.CODE} + {it.IS_TAX_FREE === "Y" + ? 면세 + : 과세} + + {qty === 0 ? "-" : ( + + )} +
+ )}