feat(frontend/logistics): render remark via category label map
This commit is contained in:
@@ -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<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchFilters, setSearchFilters] = useState<FilterValue[]>([]);
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [stockLoading, setStockLoading] = useState(false);
|
||||
@@ -750,7 +758,7 @@ export default function InventoryStatusPage() {
|
||||
{h.reference_number || h.reference_no || ""}
|
||||
</TableCell>
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
{parseRemark(h.remark) || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchFilters, setSearchFilters] = useState<FilterValue[]>([]);
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [stockLoading, setStockLoading] = useState(false);
|
||||
@@ -753,7 +761,7 @@ export default function InventoryStatusPage() {
|
||||
{h.reference_number || h.reference_no || ""}
|
||||
</TableCell>
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
{parseRemark(h.remark) || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchFilters, setSearchFilters] = useState<FilterValue[]>([]);
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [stockLoading, setStockLoading] = useState(false);
|
||||
@@ -750,7 +758,7 @@ export default function InventoryStatusPage() {
|
||||
{h.reference_number || h.reference_no || ""}
|
||||
</TableCell>
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
{parseRemark(h.remark) || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchFilters, setSearchFilters] = useState<FilterValue[]>([]);
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [stockLoading, setStockLoading] = useState(false);
|
||||
@@ -759,7 +767,7 @@ export default function InventoryStatusPage() {
|
||||
{h.reference_number || h.reference_no || ""}
|
||||
</TableCell>
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
{parseRemark(h.remark) || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchFilters, setSearchFilters] = useState<FilterValue[]>([]);
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [stockLoading, setStockLoading] = useState(false);
|
||||
@@ -753,7 +761,7 @@ export default function InventoryStatusPage() {
|
||||
{h.reference_number || h.reference_no || ""}
|
||||
</TableCell>
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
{parseRemark(h.remark) || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchFilters, setSearchFilters] = useState<FilterValue[]>([]);
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [stockLoading, setStockLoading] = useState(false);
|
||||
@@ -752,7 +760,7 @@ export default function InventoryStatusPage() {
|
||||
{h.reference_number || h.reference_no || ""}
|
||||
</TableCell>
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
{parseRemark(h.remark) || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchFilters, setSearchFilters] = useState<FilterValue[]>([]);
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
const [stockLoading, setStockLoading] = useState(false);
|
||||
@@ -750,7 +758,7 @@ export default function InventoryStatusPage() {
|
||||
{h.reference_number || h.reference_no || ""}
|
||||
</TableCell>
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
{parseRemark(h.remark) || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
90
frontend/lib/categoryLabel.ts
Normal file
90
frontend/lib/categoryLabel.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
|
||||
export type CategoryLabelMap = Record<string, string>; // 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<CategoryLabelMap>({});
|
||||
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;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user