Enhance logistics and production pages with additional mappings and data aggregation

- Added Korean labels for `shipmentInstruction` and `purchase_detail` in the logistics pages to improve clarity.
- Introduced a new constant `WOPR_TABLE` in the production result page for better data management.
- Implemented daily aggregation of `work_order_process_result` data to enrich the `work_order_process` rows, enhancing data accuracy and user experience.

These updates improve the usability and data handling in the logistics and production modules.
This commit is contained in:
kjs
2026-04-29 18:48:05 +09:00
parent 6ddc84f285
commit 5f9c876f9e
5 changed files with 42 additions and 6 deletions

View File

@@ -123,6 +123,7 @@ const getStatusColor = (status: string) => OUTBOUND_STATUS_OPTIONS.find((s) => s
// 소스 테이블 한글명 매핑
const SOURCE_TYPE_LABEL: Record<string, string> = {
shipment_instruction_detail: "출하지시",
shipmentInstruction: "출하지시",
purchase_order_mng: "발주",
item_info: "품목",
};

View File

@@ -259,6 +259,7 @@ const getStatusVariant = (status: string): "default" | "secondary" | "outline" |
// 소스 테이블 한글명 매핑
const SOURCE_TABLE_LABEL: Record<string, string> = {
purchase_order_mng: "발주",
purchase_detail: "발주",
shipment_instruction_detail: "출하",
item_info: "품목",
};

View File

@@ -33,6 +33,7 @@ import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSea
const WI_TABLE = "work_instruction";
const WOP_TABLE = "work_order_process";
const WOPR_TABLE = "work_order_process_result";
const fmtNum = (v: any) => {
const n = Number(v);
@@ -194,7 +195,36 @@ export default function ProductionResultPage() {
autoFilter: true,
sort: { columnName: "seq_no", order: "asc" },
});
setProcessData(res.data?.data?.data || res.data?.data?.rows || []);
const wopRows: any[] = res.data?.data?.data || res.data?.data?.rows || [];
// wopr 일별 실적 집계 → wop별 합산하여 wop row에 덮어씀
const woprAgg = new Map<string, { good: number; defect: number; input: number }>();
for (const w of wopRows) {
if (!w.id) continue;
try {
const wr = await apiClient.post(`/table-management/tables/${WOPR_TABLE}/data`, {
page: 1, size: 0,
dataFilter: { enabled: true, filters: [{ columnName: "wop_id", operator: "equals", value: w.id }] },
autoFilter: true,
});
const rows: any[] = wr.data?.data?.data || wr.data?.data?.rows || [];
const sum = rows.reduce(
(a, r) => ({
good: a.good + (Number(r.good_qty) || 0),
defect: a.defect + (Number(r.defect_qty) || 0),
input: a.input + (Number(r.input_qty) || 0),
}),
{ good: 0, defect: 0, input: 0 },
);
if (sum.good > 0 || sum.defect > 0 || sum.input > 0) woprAgg.set(w.id, sum);
} catch { /* skip */ }
}
const enriched = wopRows.map((w) => {
const agg = woprAgg.get(w.id);
if (!agg) return w;
return { ...w, good_qty: agg.good, defect_qty: agg.defect, input_qty: agg.input };
});
setProcessData(enriched);
} catch { setProcessData([]); }
finally { setProcessLoading(false); }
};

View File

@@ -1,5 +1,5 @@
import { Button } from "@/components/ui/button";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { Sheet, SheetContent, SheetTrigger, SheetHeader, SheetTitle } from "@/components/ui/sheet";
import { Menu } from "lucide-react";
import { MenuItem } from "@/types/menu";
import { LAYOUT_CONFIG } from "@/constants/layout";
@@ -44,6 +44,9 @@ export function SideMenu({
</Button>
</SheetTrigger>
<SheetContent side="left" className="w-64 p-0">
<SheetHeader className="sr-only">
<SheetTitle></SheetTitle>
</SheetHeader>
<div className="flex h-full flex-col">
<div className="border-b p-4">
<h2 className="font-semibold">{LAYOUT_CONFIG.COMPANY_NAME}</h2>

View File

@@ -35,11 +35,12 @@ function CommandDialog({
}) {
return (
<Dialog {...props}>
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogContent className={cn("overflow-hidden p-0", className)} showCloseButton={showCloseButton}>
{/* DialogTitle은 DialogContent 내부에 있어야 Radix UI가 a11y 인식 (sr-only로 시각적 숨김) */}
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>