From de660679cacda51182d2264d33088c7b4f05a97a Mon Sep 17 00:00:00 2001 From: kmh Date: Fri, 24 Apr 2026 15:00:42 +0900 Subject: [PATCH] feat(frontend/logistics): render remark via category label map --- .../logistics/inbound-outbound/page.tsx | 8 ++ .../COMPANY_10/logistics/inventory/page.tsx | 12 ++- .../logistics/inbound-outbound/page.tsx | 8 ++ .../COMPANY_16/logistics/inventory/page.tsx | 12 ++- .../logistics/inbound-outbound/page.tsx | 8 ++ .../COMPANY_29/logistics/inventory/page.tsx | 12 ++- .../logistics/inbound-outbound/page.tsx | 8 ++ .../COMPANY_30/logistics/inventory/page.tsx | 12 ++- .../logistics/inbound-outbound/page.tsx | 8 ++ .../COMPANY_7/logistics/inventory/page.tsx | 12 ++- .../logistics/inbound-outbound/page.tsx | 8 ++ .../COMPANY_8/logistics/inventory/page.tsx | 12 ++- .../logistics/inbound-outbound/page.tsx | 8 ++ .../COMPANY_9/logistics/inventory/page.tsx | 12 ++- frontend/lib/categoryLabel.ts | 90 +++++++++++++++++++ 15 files changed, 216 insertions(+), 14 deletions(-) create mode 100644 frontend/lib/categoryLabel.ts diff --git a/frontend/app/(main)/COMPANY_10/logistics/inbound-outbound/page.tsx b/frontend/app/(main)/COMPANY_10/logistics/inbound-outbound/page.tsx index 806bb52d..0d60b6a3 100644 --- a/frontend/app/(main)/COMPANY_10/logistics/inbound-outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_10/logistics/inbound-outbound/page.tsx @@ -23,6 +23,7 @@ import { useAuth } from "@/hooks/useAuth"; import { toast } from "sonner"; import { exportToExcel } from "@/lib/utils/excelExport"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; const HISTORY_TABLE = "inventory_history"; @@ -65,6 +66,13 @@ const parseRemark = (remark: string | null | undefined): string => { export default function InboundOutboundPage() { const { user } = useAuth(); + // remark의 value_code → value_label 변환용 카테고리 맵 (상위 parseRemark 를 shadow) + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [searchFilters, setSearchFilters] = useState([]); diff --git a/frontend/app/(main)/COMPANY_10/logistics/inventory/page.tsx b/frontend/app/(main)/COMPANY_10/logistics/inventory/page.tsx index 9e4b6977..d5d36390 100644 --- a/frontend/app/(main)/COMPANY_10/logistics/inventory/page.tsx +++ b/frontend/app/(main)/COMPANY_10/logistics/inventory/page.tsx @@ -9,7 +9,7 @@ * ★ 재고 직접 등록/삭제 불가 — 입출고를 통해서만 변동, 조정만 가능 */ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; @@ -62,6 +62,7 @@ import { TableSettingsModal } from "@/components/common/TableSettingsModal"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable"; import { toast } from "sonner"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; import { exportToExcel } from "@/lib/utils/excelExport"; const STOCK_TABLE = "inventory_stock"; @@ -118,6 +119,13 @@ export default function InventoryStatusPage() { const { user } = useAuth(); const ts = useTableSettings("c16-inventory", STOCK_TABLE, STOCK_COLUMNS); + // remark의 value_code → value_label 변환 파서 + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + // 좌측: 재고 목록 const [stockItems, setStockItems] = useState([]); const [stockLoading, setStockLoading] = useState(false); @@ -750,7 +758,7 @@ export default function InventoryStatusPage() { {h.reference_number || h.reference_no || ""} - {h.remark || h.reason || ""} + {parseRemark(h.remark) || h.reason || ""} {userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""} diff --git a/frontend/app/(main)/COMPANY_16/logistics/inbound-outbound/page.tsx b/frontend/app/(main)/COMPANY_16/logistics/inbound-outbound/page.tsx index 806bb52d..0d60b6a3 100644 --- a/frontend/app/(main)/COMPANY_16/logistics/inbound-outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_16/logistics/inbound-outbound/page.tsx @@ -23,6 +23,7 @@ import { useAuth } from "@/hooks/useAuth"; import { toast } from "sonner"; import { exportToExcel } from "@/lib/utils/excelExport"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; const HISTORY_TABLE = "inventory_history"; @@ -65,6 +66,13 @@ const parseRemark = (remark: string | null | undefined): string => { export default function InboundOutboundPage() { const { user } = useAuth(); + // remark의 value_code → value_label 변환용 카테고리 맵 (상위 parseRemark 를 shadow) + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [searchFilters, setSearchFilters] = useState([]); diff --git a/frontend/app/(main)/COMPANY_16/logistics/inventory/page.tsx b/frontend/app/(main)/COMPANY_16/logistics/inventory/page.tsx index 5b6e6c41..a2b8d5ca 100644 --- a/frontend/app/(main)/COMPANY_16/logistics/inventory/page.tsx +++ b/frontend/app/(main)/COMPANY_16/logistics/inventory/page.tsx @@ -9,7 +9,7 @@ * ★ 재고 직접 등록/삭제 불가 — 입출고를 통해서만 변동, 조정만 가능 */ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; @@ -62,6 +62,7 @@ import { TableSettingsModal } from "@/components/common/TableSettingsModal"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable"; import { toast } from "sonner"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; import { exportToExcel } from "@/lib/utils/excelExport"; const STOCK_TABLE = "inventory_stock"; @@ -118,6 +119,13 @@ export default function InventoryStatusPage() { const { user } = useAuth(); const ts = useTableSettings("c16-inventory", STOCK_TABLE, STOCK_COLUMNS); + // remark의 value_code → value_label 변환 파서 + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + // 좌측: 재고 목록 const [stockItems, setStockItems] = useState([]); const [stockLoading, setStockLoading] = useState(false); @@ -753,7 +761,7 @@ export default function InventoryStatusPage() { {h.reference_number || h.reference_no || ""} - {h.remark || h.reason || ""} + {parseRemark(h.remark) || h.reason || ""} {userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""} diff --git a/frontend/app/(main)/COMPANY_29/logistics/inbound-outbound/page.tsx b/frontend/app/(main)/COMPANY_29/logistics/inbound-outbound/page.tsx index 806bb52d..0d60b6a3 100644 --- a/frontend/app/(main)/COMPANY_29/logistics/inbound-outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_29/logistics/inbound-outbound/page.tsx @@ -23,6 +23,7 @@ import { useAuth } from "@/hooks/useAuth"; import { toast } from "sonner"; import { exportToExcel } from "@/lib/utils/excelExport"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; const HISTORY_TABLE = "inventory_history"; @@ -65,6 +66,13 @@ const parseRemark = (remark: string | null | undefined): string => { export default function InboundOutboundPage() { const { user } = useAuth(); + // remark의 value_code → value_label 변환용 카테고리 맵 (상위 parseRemark 를 shadow) + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [searchFilters, setSearchFilters] = useState([]); diff --git a/frontend/app/(main)/COMPANY_29/logistics/inventory/page.tsx b/frontend/app/(main)/COMPANY_29/logistics/inventory/page.tsx index 9e4b6977..d5d36390 100644 --- a/frontend/app/(main)/COMPANY_29/logistics/inventory/page.tsx +++ b/frontend/app/(main)/COMPANY_29/logistics/inventory/page.tsx @@ -9,7 +9,7 @@ * ★ 재고 직접 등록/삭제 불가 — 입출고를 통해서만 변동, 조정만 가능 */ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; @@ -62,6 +62,7 @@ import { TableSettingsModal } from "@/components/common/TableSettingsModal"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable"; import { toast } from "sonner"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; import { exportToExcel } from "@/lib/utils/excelExport"; const STOCK_TABLE = "inventory_stock"; @@ -118,6 +119,13 @@ export default function InventoryStatusPage() { const { user } = useAuth(); const ts = useTableSettings("c16-inventory", STOCK_TABLE, STOCK_COLUMNS); + // remark의 value_code → value_label 변환 파서 + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + // 좌측: 재고 목록 const [stockItems, setStockItems] = useState([]); const [stockLoading, setStockLoading] = useState(false); @@ -750,7 +758,7 @@ export default function InventoryStatusPage() { {h.reference_number || h.reference_no || ""} - {h.remark || h.reason || ""} + {parseRemark(h.remark) || h.reason || ""} {userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""} diff --git a/frontend/app/(main)/COMPANY_30/logistics/inbound-outbound/page.tsx b/frontend/app/(main)/COMPANY_30/logistics/inbound-outbound/page.tsx index bbad370b..271cdb78 100644 --- a/frontend/app/(main)/COMPANY_30/logistics/inbound-outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_30/logistics/inbound-outbound/page.tsx @@ -23,6 +23,7 @@ import { useAuth } from "@/hooks/useAuth"; import { toast } from "sonner"; import { exportToExcel } from "@/lib/utils/excelExport"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; const HISTORY_TABLE = "inventory_history"; @@ -65,6 +66,13 @@ const parseRemark = (remark: string | null | undefined): string => { export default function InboundOutboundPage() { const { user } = useAuth(); + // remark의 value_code → value_label 변환용 카테고리 맵 (상위 parseRemark 를 shadow) + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [searchFilters, setSearchFilters] = useState([]); diff --git a/frontend/app/(main)/COMPANY_30/logistics/inventory/page.tsx b/frontend/app/(main)/COMPANY_30/logistics/inventory/page.tsx index f41c3439..78c41b96 100644 --- a/frontend/app/(main)/COMPANY_30/logistics/inventory/page.tsx +++ b/frontend/app/(main)/COMPANY_30/logistics/inventory/page.tsx @@ -9,7 +9,7 @@ * ★ 재고 직접 등록/삭제 불가 — 입출고를 통해서만 변동, 조정만 가능 */ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; @@ -62,6 +62,7 @@ import { TableSettingsModal } from "@/components/common/TableSettingsModal"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable"; import { toast } from "sonner"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; import { exportToExcel } from "@/lib/utils/excelExport"; const STOCK_TABLE = "inventory_stock"; @@ -121,6 +122,13 @@ export default function InventoryStatusPage() { const { user } = useAuth(); const ts = useTableSettings("c16-inventory", STOCK_TABLE, STOCK_COLUMNS); + // remark의 value_code → value_label 변환 파서 + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + // 좌측: 재고 목록 const [stockItems, setStockItems] = useState([]); const [stockLoading, setStockLoading] = useState(false); @@ -759,7 +767,7 @@ export default function InventoryStatusPage() { {h.reference_number || h.reference_no || ""} - {h.remark || h.reason || ""} + {parseRemark(h.remark) || h.reason || ""} {userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""} diff --git a/frontend/app/(main)/COMPANY_7/logistics/inbound-outbound/page.tsx b/frontend/app/(main)/COMPANY_7/logistics/inbound-outbound/page.tsx index 806bb52d..f3b3475f 100644 --- a/frontend/app/(main)/COMPANY_7/logistics/inbound-outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_7/logistics/inbound-outbound/page.tsx @@ -23,6 +23,7 @@ import { useAuth } from "@/hooks/useAuth"; import { toast } from "sonner"; import { exportToExcel } from "@/lib/utils/excelExport"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; const HISTORY_TABLE = "inventory_history"; @@ -65,6 +66,13 @@ const parseRemark = (remark: string | null | undefined): string => { export default function InboundOutboundPage() { const { user } = useAuth(); + // remark의 value_code → value_label 변환용 카테고리 맵 (컴포넌트 내부에서 상위 parseRemark 를 shadow) + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [searchFilters, setSearchFilters] = useState([]); diff --git a/frontend/app/(main)/COMPANY_7/logistics/inventory/page.tsx b/frontend/app/(main)/COMPANY_7/logistics/inventory/page.tsx index 5b6e6c41..a2b8d5ca 100644 --- a/frontend/app/(main)/COMPANY_7/logistics/inventory/page.tsx +++ b/frontend/app/(main)/COMPANY_7/logistics/inventory/page.tsx @@ -9,7 +9,7 @@ * ★ 재고 직접 등록/삭제 불가 — 입출고를 통해서만 변동, 조정만 가능 */ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; @@ -62,6 +62,7 @@ import { TableSettingsModal } from "@/components/common/TableSettingsModal"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable"; import { toast } from "sonner"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; import { exportToExcel } from "@/lib/utils/excelExport"; const STOCK_TABLE = "inventory_stock"; @@ -118,6 +119,13 @@ export default function InventoryStatusPage() { const { user } = useAuth(); const ts = useTableSettings("c16-inventory", STOCK_TABLE, STOCK_COLUMNS); + // remark의 value_code → value_label 변환 파서 + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + // 좌측: 재고 목록 const [stockItems, setStockItems] = useState([]); const [stockLoading, setStockLoading] = useState(false); @@ -753,7 +761,7 @@ export default function InventoryStatusPage() { {h.reference_number || h.reference_no || ""} - {h.remark || h.reason || ""} + {parseRemark(h.remark) || h.reason || ""} {userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""} diff --git a/frontend/app/(main)/COMPANY_8/logistics/inbound-outbound/page.tsx b/frontend/app/(main)/COMPANY_8/logistics/inbound-outbound/page.tsx index a6cf4b13..a80772f1 100644 --- a/frontend/app/(main)/COMPANY_8/logistics/inbound-outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_8/logistics/inbound-outbound/page.tsx @@ -23,6 +23,7 @@ import { useAuth } from "@/hooks/useAuth"; import { toast } from "sonner"; import { exportToExcel } from "@/lib/utils/excelExport"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; const HISTORY_TABLE = "inventory_history"; @@ -65,6 +66,13 @@ const parseRemark = (remark: string | null | undefined): string => { export default function InboundOutboundPage() { const { user } = useAuth(); + // remark의 value_code → value_label 변환용 카테고리 맵 (상위 parseRemark 를 shadow) + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [searchFilters, setSearchFilters] = useState([]); diff --git a/frontend/app/(main)/COMPANY_8/logistics/inventory/page.tsx b/frontend/app/(main)/COMPANY_8/logistics/inventory/page.tsx index 740ecf68..b89834f2 100644 --- a/frontend/app/(main)/COMPANY_8/logistics/inventory/page.tsx +++ b/frontend/app/(main)/COMPANY_8/logistics/inventory/page.tsx @@ -9,7 +9,7 @@ * ★ 재고 직접 등록/삭제 불가 — 입출고를 통해서만 변동, 조정만 가능 */ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; @@ -62,6 +62,7 @@ import { TableSettingsModal } from "@/components/common/TableSettingsModal"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable"; import { toast } from "sonner"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; import { exportToExcel } from "@/lib/utils/excelExport"; const STOCK_TABLE = "inventory_stock"; @@ -118,6 +119,13 @@ export default function InventoryStatusPage() { const { user } = useAuth(); const ts = useTableSettings("c16-inventory", STOCK_TABLE, STOCK_COLUMNS); + // remark의 value_code → value_label 변환 파서 + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + // 좌측: 재고 목록 const [stockItems, setStockItems] = useState([]); const [stockLoading, setStockLoading] = useState(false); @@ -752,7 +760,7 @@ export default function InventoryStatusPage() { {h.reference_number || h.reference_no || ""} - {h.remark || h.reason || ""} + {parseRemark(h.remark) || h.reason || ""} {userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""} diff --git a/frontend/app/(main)/COMPANY_9/logistics/inbound-outbound/page.tsx b/frontend/app/(main)/COMPANY_9/logistics/inbound-outbound/page.tsx index bbad370b..271cdb78 100644 --- a/frontend/app/(main)/COMPANY_9/logistics/inbound-outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_9/logistics/inbound-outbound/page.tsx @@ -23,6 +23,7 @@ import { useAuth } from "@/hooks/useAuth"; import { toast } from "sonner"; import { exportToExcel } from "@/lib/utils/excelExport"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; const HISTORY_TABLE = "inventory_history"; @@ -65,6 +66,13 @@ const parseRemark = (remark: string | null | undefined): string => { export default function InboundOutboundPage() { const { user } = useAuth(); + // remark의 value_code → value_label 변환용 카테고리 맵 (상위 parseRemark 를 shadow) + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [searchFilters, setSearchFilters] = useState([]); diff --git a/frontend/app/(main)/COMPANY_9/logistics/inventory/page.tsx b/frontend/app/(main)/COMPANY_9/logistics/inventory/page.tsx index 73a8c8b3..69bbad1a 100644 --- a/frontend/app/(main)/COMPANY_9/logistics/inventory/page.tsx +++ b/frontend/app/(main)/COMPANY_9/logistics/inventory/page.tsx @@ -9,7 +9,7 @@ * ★ 재고 직접 등록/삭제 불가 — 입출고를 통해서만 변동, 조정만 가능 */ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; @@ -62,6 +62,7 @@ import { TableSettingsModal } from "@/components/common/TableSettingsModal"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable"; import { toast } from "sonner"; +import { useCategoryLabelMap, makeParseRemark } from "@/lib/categoryLabel"; import { exportToExcel } from "@/lib/utils/excelExport"; const STOCK_TABLE = "inventory_stock"; @@ -118,6 +119,13 @@ export default function InventoryStatusPage() { const { user } = useAuth(); const ts = useTableSettings("c16-inventory", STOCK_TABLE, STOCK_COLUMNS); + // remark의 value_code → value_label 변환 파서 + const codeLabelMap = useCategoryLabelMap([ + { table: "inbound_mng", column: "inbound_type" }, + { table: "outbound_mng", column: "outbound_type" }, + ]); + const parseRemark = useMemo(() => makeParseRemark(codeLabelMap), [codeLabelMap]); + // 좌측: 재고 목록 const [stockItems, setStockItems] = useState([]); const [stockLoading, setStockLoading] = useState(false); @@ -750,7 +758,7 @@ export default function InventoryStatusPage() { {h.reference_number || h.reference_no || ""} - {h.remark || h.reason || ""} + {parseRemark(h.remark) || h.reason || ""} {userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""} diff --git a/frontend/lib/categoryLabel.ts b/frontend/lib/categoryLabel.ts new file mode 100644 index 00000000..f0045d1c --- /dev/null +++ b/frontend/lib/categoryLabel.ts @@ -0,0 +1,90 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { apiClient } from "@/lib/api/client"; + +export type CategoryLabelMap = Record; // value_code -> value_label + +export interface CategoryTarget { + table: string; + column: string; +} + +/** + * 여러 (table, column) 쌍의 카테고리 값을 한 번에 조회해서 + * value_code -> value_label 통합 맵을 반환한다. + */ +export function useCategoryLabelMap(targets: CategoryTarget[]): CategoryLabelMap { + const [map, setMap] = useState({}); + const key = useMemo(() => JSON.stringify(targets), [targets]); + + useEffect(() => { + let cancelled = false; + (async () => { + const merged: CategoryLabelMap = {}; + for (const { table, column } of targets) { + try { + const res = await apiClient.get( + `/table-categories/${table}/${column}/values`, + ); + if (res.data?.success && Array.isArray(res.data.data)) { + const flatten = (vals: any[]) => { + for (const v of vals) { + if (v.valueCode && v.valueLabel) { + merged[v.valueCode] = v.valueLabel; + } + if (v.children?.length) flatten(v.children); + } + }; + flatten(res.data.data); + } + } catch { + /* skip */ + } + } + if (!cancelled) setMap(merged); + })(); + return () => { + cancelled = true; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [key]); + + return map; +} + +/** + * remark 원문을 화면 표시용 문자열로 변환하는 파서 팩토리. + * - JSON remark → 사람 읽을 수 있는 한글 (창고이동/재고조정/재고확인/공정입고) + * - value_code → codeLabelMap 에서 찾은 value_label + * - 매칭 없음 → 원문 그대로 (과거 한글 레코드 호환) + */ +export function makeParseRemark(codeLabelMap: CategoryLabelMap) { + return (remark: string | null | undefined): string => { + if (!remark) return ""; + const trimmed = remark.trim(); + + if (trimmed.startsWith("{")) { + try { + const d = JSON.parse(trimmed); + switch (d.type) { + case "move": + return `창고이동 (${d.from_warehouse} → ${d.to_warehouse})`; + case "adjust": + return `재고조정 (${d.reason || "사유 없음"}, ${d.system_qty}→${d.actual_qty}, 차이:${d.diff >= 0 ? "+" : ""}${d.diff})`; + case "confirm": + return `재고확인 (${d.reason || "이상없음"})`; + case "process_inbound": + return "공정입고"; + default: + return d.reason || d.memo || trimmed; + } + } catch { + return trimmed; + } + } + + if (codeLabelMap[trimmed]) return codeLabelMap[trimmed]; + return trimmed; + }; +}