refactor: Update table column widths and improve pagination settings in purchase and sales order pages
- Changed column width definitions from fixed to minimum widths for better responsiveness in the purchase order and sales order pages. - Increased the pagination size from 500 to 5000 for supplier and user data fetching to accommodate larger datasets. - Enhanced item search functionality by including management item filters in server queries, improving data handling and user experience. - These changes aim to provide a more flexible and user-friendly interface across multiple company implementations.
This commit is contained in:
@@ -88,18 +88,18 @@ const GRID_COLUMNS_CONFIG = [
|
||||
];
|
||||
|
||||
const MODAL_DETAIL_COLUMNS = [
|
||||
{ key: "item_code", label: "품번", width: "w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "w-[120px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "w-[60px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "w-[120px]" },
|
||||
{ key: "item_code", label: "품번", width: "min-w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "min-w-[150px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "min-w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "min-w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "min-w-[90px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "min-w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "min-w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "min-w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "min-w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "min-w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "min-w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "min-w-[120px]" },
|
||||
];
|
||||
|
||||
const MODAL_COL_ORDER_KEY = "purchase_order_modal_col_order_c16";
|
||||
@@ -237,7 +237,7 @@ export default function PurchaseOrderPage() {
|
||||
);
|
||||
try {
|
||||
const suppRes = await apiClient.post(`/table-management/tables/supplier_mng/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const supps = suppRes.data?.data?.data || suppRes.data?.data?.rows || [];
|
||||
optMap["supplier_code"] = supps.map((s: any) => ({
|
||||
@@ -247,7 +247,7 @@ export default function PurchaseOrderPage() {
|
||||
} catch { /* skip */ }
|
||||
try {
|
||||
const userRes = await apiClient.post(`/table-management/tables/user_info/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
optMap["manager"] = users.map((u: any) => ({
|
||||
@@ -293,7 +293,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
|
||||
const res = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: detailFilters.length > 0 ? { enabled: true, filters: detailFilters } : undefined,
|
||||
autoFilter: true,
|
||||
sort: { columnName: "purchase_no", order: "desc" },
|
||||
@@ -538,7 +538,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// 품목 검색
|
||||
// 품목 검색 (수주관리와 동일한 서버 페이징 방식)
|
||||
const searchItems = async (page?: number, size?: number) => {
|
||||
const p = page ?? itemPage;
|
||||
const s = size ?? itemPageSize;
|
||||
@@ -548,25 +548,24 @@ export default function PurchaseOrderPage() {
|
||||
if (itemSearchKeyword) {
|
||||
filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword });
|
||||
}
|
||||
// 관리품목 필터를 서버 쿼리에 포함 (코드 + 라벨 양쪽 대응)
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
const divValues = [itemSearchDivision];
|
||||
if (divLabel) divValues.push(divLabel);
|
||||
filters.push({ columnName: "division", operator: "in", value: divValues });
|
||||
}
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: p, size: s,
|
||||
dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
|
||||
autoFilter: true,
|
||||
});
|
||||
const resData = res.data?.data;
|
||||
let allRows = resData?.data || resData?.rows || [];
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
allRows = allRows.filter((item: any) => {
|
||||
const div = item.division || "";
|
||||
return div.includes(itemSearchDivision) || (divLabel && div.includes(divLabel));
|
||||
});
|
||||
}
|
||||
const total = allRows.length;
|
||||
const start = (p - 1) * s;
|
||||
setItemSearchResults(allRows.slice(start, start + s));
|
||||
setItemTotal(total);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(total / s)));
|
||||
const rows = resData?.data || resData?.rows || [];
|
||||
const serverTotal = resData?.total || resData?.totalCount || rows.length;
|
||||
setItemSearchResults(rows);
|
||||
setItemTotal(serverTotal);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(serverTotal / s)));
|
||||
} catch { /* skip */ } finally {
|
||||
setItemSearchLoading(false);
|
||||
}
|
||||
@@ -608,7 +607,7 @@ export default function PurchaseOrderPage() {
|
||||
try {
|
||||
const itemIds = selected.map((item) => item.item_number || item.id);
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: {
|
||||
enabled: true,
|
||||
filters: [
|
||||
@@ -671,7 +670,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "item_number", operator: "in", value: itemCodes }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
@@ -693,7 +692,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [
|
||||
{ columnName: "supplier_id", operator: "equals", value: supplierCode },
|
||||
{ columnName: "item_id", operator: "in", value: itemCodes },
|
||||
@@ -1037,7 +1036,7 @@ export default function PurchaseOrderPage() {
|
||||
) : (
|
||||
<div className="border rounded-lg overflow-x-auto">
|
||||
<DndContext sensors={modalSensors} collisionDetection={closestCenter} onDragEnd={handleModalDragEnd}>
|
||||
<Table className="table-fixed">
|
||||
<Table>
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<SortableContext items={visibleModalColumns.map((c) => c.key)} strategy={horizontalListSortingStrategy}>
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
@@ -1061,12 +1060,12 @@ export default function PurchaseOrderPage() {
|
||||
{visibleModalColumns.map((col) => {
|
||||
switch (col.key) {
|
||||
case "item_code":
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono max-w-[120px]"><span className="block truncate" title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono whitespace-nowrap"><span title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
case "item_name":
|
||||
return <TableCell key={col.key} className="text-[13px] max-w-[120px]"><span className="block truncate" title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] whitespace-nowrap"><span title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
case "supplier":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[150px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{(categoryOptions["supplier_code"] || []).find(o => o.code === row.supplier_code)?.label || row.supplier_code || "-"}</span>
|
||||
) : (
|
||||
@@ -1076,7 +1075,7 @@ export default function PurchaseOrderPage() {
|
||||
updateDetailRow(idx, "supplier_code", v);
|
||||
updateDetailRow(idx, "supplier_name", name);
|
||||
}}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["supplier_code"] || []).map(o => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1092,11 +1091,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px]">{row.unit}</TableCell>;
|
||||
case "order_qty":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[110px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.order_qty ? Number(row.order_qty).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1106,11 +1105,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono">{row.remain_qty ? Number(row.remain_qty).toLocaleString() : "0"}</TableCell>;
|
||||
case "unit_price":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[120px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.unit_price ? Number(row.unit_price).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1118,21 +1117,21 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono font-semibold">{row.amount ? Number(row.amount).toLocaleString() : ""}</TableCell>;
|
||||
case "due_date":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[160px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.due_date}</span>
|
||||
) : (
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs" />
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
case "memo":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[140px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.memo}</span>
|
||||
) : (
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs" />
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
|
||||
@@ -1320,44 +1320,44 @@ export default function SalesOrderPage() {
|
||||
<Table noWrapper className="min-w-max w-full">
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
<TableHead className="w-10 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="w-28 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="w-24 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="w-24 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
<TableHead className="min-w-[40px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="min-w-[120px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="min-w-[150px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="min-w-[110px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="min-w-[100px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="min-w-[90px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="min-w-[120px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="min-w-[160px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="min-w-[110px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{detailRows.map((row, idx) => (
|
||||
<TableRow key={row._id || idx}>
|
||||
<TableCell className="text-center font-mono text-[13px] text-muted-foreground/50">{idx + 1}</TableCell>
|
||||
<TableCell className="max-w-[112px]">
|
||||
<span className="block truncate font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[128px]">
|
||||
<span className="block truncate text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.material}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.material}</TableCell>
|
||||
<TableCell>
|
||||
<Input
|
||||
value={row.packing_material || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "packing_material", e.target.value)}
|
||||
placeholder="포장재"
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Select value={row.unit || ""} onValueChange={(v) => updateDetailRow(idx, "unit", v)}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["item_unit"] || []).map((o) => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1371,7 +1371,7 @@ export default function SalesOrderPage() {
|
||||
min="1"
|
||||
value={row.qty || "1"}
|
||||
onChange={(e) => updateDetailRow(idx, "qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1380,7 +1380,7 @@ export default function SalesOrderPage() {
|
||||
min="0"
|
||||
value={row.pack_qty || "0"}
|
||||
onChange={(e) => updateDetailRow(idx, "pack_qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1388,10 +1388,10 @@ export default function SalesOrderPage() {
|
||||
value={formatNumber(row.unit_price || "")}
|
||||
onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))}
|
||||
readOnly={!allowPriceEdit}
|
||||
className={cn("h-8 text-xs text-right font-mono w-20", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
className={cn("h-8 text-xs text-right font-mono w-full", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold">
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold whitespace-nowrap">
|
||||
{row.amount ? Number(row.amount).toLocaleString() : "0"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1399,7 +1399,7 @@ export default function SalesOrderPage() {
|
||||
type="date"
|
||||
value={row.due_date || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
|
||||
@@ -88,18 +88,18 @@ const GRID_COLUMNS_CONFIG = [
|
||||
];
|
||||
|
||||
const MODAL_DETAIL_COLUMNS = [
|
||||
{ key: "item_code", label: "품번", width: "w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "w-[120px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "w-[60px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "w-[120px]" },
|
||||
{ key: "item_code", label: "품번", width: "min-w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "min-w-[150px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "min-w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "min-w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "min-w-[90px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "min-w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "min-w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "min-w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "min-w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "min-w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "min-w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "min-w-[120px]" },
|
||||
];
|
||||
|
||||
const MODAL_COL_ORDER_KEY = "purchase_order_modal_col_order_c16";
|
||||
@@ -237,7 +237,7 @@ export default function PurchaseOrderPage() {
|
||||
);
|
||||
try {
|
||||
const suppRes = await apiClient.post(`/table-management/tables/supplier_mng/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const supps = suppRes.data?.data?.data || suppRes.data?.data?.rows || [];
|
||||
optMap["supplier_code"] = supps.map((s: any) => ({
|
||||
@@ -247,7 +247,7 @@ export default function PurchaseOrderPage() {
|
||||
} catch { /* skip */ }
|
||||
try {
|
||||
const userRes = await apiClient.post(`/table-management/tables/user_info/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
optMap["manager"] = users.map((u: any) => ({
|
||||
@@ -293,7 +293,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
|
||||
const res = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: detailFilters.length > 0 ? { enabled: true, filters: detailFilters } : undefined,
|
||||
autoFilter: true,
|
||||
sort: { columnName: "purchase_no", order: "desc" },
|
||||
@@ -538,7 +538,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// 품목 검색
|
||||
// 품목 검색 (수주관리와 동일한 서버 페이징 방식)
|
||||
const searchItems = async (page?: number, size?: number) => {
|
||||
const p = page ?? itemPage;
|
||||
const s = size ?? itemPageSize;
|
||||
@@ -548,25 +548,24 @@ export default function PurchaseOrderPage() {
|
||||
if (itemSearchKeyword) {
|
||||
filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword });
|
||||
}
|
||||
// 관리품목 필터를 서버 쿼리에 포함 (코드 + 라벨 양쪽 대응)
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
const divValues = [itemSearchDivision];
|
||||
if (divLabel) divValues.push(divLabel);
|
||||
filters.push({ columnName: "division", operator: "in", value: divValues });
|
||||
}
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: p, size: s,
|
||||
dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
|
||||
autoFilter: true,
|
||||
});
|
||||
const resData = res.data?.data;
|
||||
let allRows = resData?.data || resData?.rows || [];
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
allRows = allRows.filter((item: any) => {
|
||||
const div = item.division || "";
|
||||
return div.includes(itemSearchDivision) || (divLabel && div.includes(divLabel));
|
||||
});
|
||||
}
|
||||
const total = allRows.length;
|
||||
const start = (p - 1) * s;
|
||||
setItemSearchResults(allRows.slice(start, start + s));
|
||||
setItemTotal(total);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(total / s)));
|
||||
const rows = resData?.data || resData?.rows || [];
|
||||
const serverTotal = resData?.total || resData?.totalCount || rows.length;
|
||||
setItemSearchResults(rows);
|
||||
setItemTotal(serverTotal);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(serverTotal / s)));
|
||||
} catch { /* skip */ } finally {
|
||||
setItemSearchLoading(false);
|
||||
}
|
||||
@@ -608,7 +607,7 @@ export default function PurchaseOrderPage() {
|
||||
try {
|
||||
const itemIds = selected.map((item) => item.item_number || item.id);
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: {
|
||||
enabled: true,
|
||||
filters: [
|
||||
@@ -671,7 +670,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "item_number", operator: "in", value: itemCodes }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
@@ -693,7 +692,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [
|
||||
{ columnName: "supplier_id", operator: "equals", value: supplierCode },
|
||||
{ columnName: "item_id", operator: "in", value: itemCodes },
|
||||
@@ -1037,7 +1036,7 @@ export default function PurchaseOrderPage() {
|
||||
) : (
|
||||
<div className="border rounded-lg overflow-x-auto">
|
||||
<DndContext sensors={modalSensors} collisionDetection={closestCenter} onDragEnd={handleModalDragEnd}>
|
||||
<Table className="table-fixed">
|
||||
<Table>
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<SortableContext items={visibleModalColumns.map((c) => c.key)} strategy={horizontalListSortingStrategy}>
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
@@ -1061,12 +1060,12 @@ export default function PurchaseOrderPage() {
|
||||
{visibleModalColumns.map((col) => {
|
||||
switch (col.key) {
|
||||
case "item_code":
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono max-w-[120px]"><span className="block truncate" title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono whitespace-nowrap"><span title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
case "item_name":
|
||||
return <TableCell key={col.key} className="text-[13px] max-w-[120px]"><span className="block truncate" title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] whitespace-nowrap"><span title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
case "supplier":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[150px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{(categoryOptions["supplier_code"] || []).find(o => o.code === row.supplier_code)?.label || row.supplier_code || "-"}</span>
|
||||
) : (
|
||||
@@ -1076,7 +1075,7 @@ export default function PurchaseOrderPage() {
|
||||
updateDetailRow(idx, "supplier_code", v);
|
||||
updateDetailRow(idx, "supplier_name", name);
|
||||
}}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["supplier_code"] || []).map(o => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1092,11 +1091,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px]">{row.unit}</TableCell>;
|
||||
case "order_qty":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[110px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.order_qty ? Number(row.order_qty).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1106,11 +1105,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono">{row.remain_qty ? Number(row.remain_qty).toLocaleString() : "0"}</TableCell>;
|
||||
case "unit_price":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[120px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.unit_price ? Number(row.unit_price).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1118,21 +1117,21 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono font-semibold">{row.amount ? Number(row.amount).toLocaleString() : ""}</TableCell>;
|
||||
case "due_date":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[160px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.due_date}</span>
|
||||
) : (
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs" />
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
case "memo":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[140px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.memo}</span>
|
||||
) : (
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs" />
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
|
||||
@@ -1320,44 +1320,44 @@ export default function SalesOrderPage() {
|
||||
<Table noWrapper className="min-w-max w-full">
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
<TableHead className="w-10 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="w-28 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="w-24 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="w-24 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
<TableHead className="min-w-[40px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="min-w-[120px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="min-w-[150px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="min-w-[110px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="min-w-[100px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="min-w-[90px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="min-w-[120px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="min-w-[160px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="min-w-[110px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{detailRows.map((row, idx) => (
|
||||
<TableRow key={row._id || idx}>
|
||||
<TableCell className="text-center font-mono text-[13px] text-muted-foreground/50">{idx + 1}</TableCell>
|
||||
<TableCell className="max-w-[112px]">
|
||||
<span className="block truncate font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[128px]">
|
||||
<span className="block truncate text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.material}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.material}</TableCell>
|
||||
<TableCell>
|
||||
<Input
|
||||
value={row.packing_material || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "packing_material", e.target.value)}
|
||||
placeholder="포장재"
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Select value={row.unit || ""} onValueChange={(v) => updateDetailRow(idx, "unit", v)}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["item_unit"] || []).map((o) => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1371,7 +1371,7 @@ export default function SalesOrderPage() {
|
||||
min="1"
|
||||
value={row.qty || "1"}
|
||||
onChange={(e) => updateDetailRow(idx, "qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1380,7 +1380,7 @@ export default function SalesOrderPage() {
|
||||
min="0"
|
||||
value={row.pack_qty || "0"}
|
||||
onChange={(e) => updateDetailRow(idx, "pack_qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1388,10 +1388,10 @@ export default function SalesOrderPage() {
|
||||
value={formatNumber(row.unit_price || "")}
|
||||
onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))}
|
||||
readOnly={!allowPriceEdit}
|
||||
className={cn("h-8 text-xs text-right font-mono w-20", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
className={cn("h-8 text-xs text-right font-mono w-full", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold">
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold whitespace-nowrap">
|
||||
{row.amount ? Number(row.amount).toLocaleString() : "0"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1399,7 +1399,7 @@ export default function SalesOrderPage() {
|
||||
type="date"
|
||||
value={row.due_date || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
|
||||
@@ -88,18 +88,18 @@ const GRID_COLUMNS_CONFIG = [
|
||||
];
|
||||
|
||||
const MODAL_DETAIL_COLUMNS = [
|
||||
{ key: "item_code", label: "품번", width: "w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "w-[120px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "w-[60px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "w-[120px]" },
|
||||
{ key: "item_code", label: "품번", width: "min-w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "min-w-[150px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "min-w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "min-w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "min-w-[90px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "min-w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "min-w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "min-w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "min-w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "min-w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "min-w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "min-w-[120px]" },
|
||||
];
|
||||
|
||||
const MODAL_COL_ORDER_KEY = "purchase_order_modal_col_order_c16";
|
||||
@@ -237,7 +237,7 @@ export default function PurchaseOrderPage() {
|
||||
);
|
||||
try {
|
||||
const suppRes = await apiClient.post(`/table-management/tables/supplier_mng/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const supps = suppRes.data?.data?.data || suppRes.data?.data?.rows || [];
|
||||
optMap["supplier_code"] = supps.map((s: any) => ({
|
||||
@@ -247,7 +247,7 @@ export default function PurchaseOrderPage() {
|
||||
} catch { /* skip */ }
|
||||
try {
|
||||
const userRes = await apiClient.post(`/table-management/tables/user_info/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
optMap["manager"] = users.map((u: any) => ({
|
||||
@@ -293,7 +293,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
|
||||
const res = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: detailFilters.length > 0 ? { enabled: true, filters: detailFilters } : undefined,
|
||||
autoFilter: true,
|
||||
sort: { columnName: "purchase_no", order: "desc" },
|
||||
@@ -538,7 +538,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// 품목 검색
|
||||
// 품목 검색 (수주관리와 동일한 서버 페이징 방식)
|
||||
const searchItems = async (page?: number, size?: number) => {
|
||||
const p = page ?? itemPage;
|
||||
const s = size ?? itemPageSize;
|
||||
@@ -548,25 +548,24 @@ export default function PurchaseOrderPage() {
|
||||
if (itemSearchKeyword) {
|
||||
filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword });
|
||||
}
|
||||
// 관리품목 필터를 서버 쿼리에 포함 (코드 + 라벨 양쪽 대응)
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
const divValues = [itemSearchDivision];
|
||||
if (divLabel) divValues.push(divLabel);
|
||||
filters.push({ columnName: "division", operator: "in", value: divValues });
|
||||
}
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: p, size: s,
|
||||
dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
|
||||
autoFilter: true,
|
||||
});
|
||||
const resData = res.data?.data;
|
||||
let allRows = resData?.data || resData?.rows || [];
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
allRows = allRows.filter((item: any) => {
|
||||
const div = item.division || "";
|
||||
return div.includes(itemSearchDivision) || (divLabel && div.includes(divLabel));
|
||||
});
|
||||
}
|
||||
const total = allRows.length;
|
||||
const start = (p - 1) * s;
|
||||
setItemSearchResults(allRows.slice(start, start + s));
|
||||
setItemTotal(total);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(total / s)));
|
||||
const rows = resData?.data || resData?.rows || [];
|
||||
const serverTotal = resData?.total || resData?.totalCount || rows.length;
|
||||
setItemSearchResults(rows);
|
||||
setItemTotal(serverTotal);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(serverTotal / s)));
|
||||
} catch { /* skip */ } finally {
|
||||
setItemSearchLoading(false);
|
||||
}
|
||||
@@ -608,7 +607,7 @@ export default function PurchaseOrderPage() {
|
||||
try {
|
||||
const itemIds = selected.map((item) => item.item_number || item.id);
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: {
|
||||
enabled: true,
|
||||
filters: [
|
||||
@@ -671,7 +670,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "item_number", operator: "in", value: itemCodes }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
@@ -693,7 +692,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [
|
||||
{ columnName: "supplier_id", operator: "equals", value: supplierCode },
|
||||
{ columnName: "item_id", operator: "in", value: itemCodes },
|
||||
@@ -1037,7 +1036,7 @@ export default function PurchaseOrderPage() {
|
||||
) : (
|
||||
<div className="border rounded-lg overflow-x-auto">
|
||||
<DndContext sensors={modalSensors} collisionDetection={closestCenter} onDragEnd={handleModalDragEnd}>
|
||||
<Table className="table-fixed">
|
||||
<Table>
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<SortableContext items={visibleModalColumns.map((c) => c.key)} strategy={horizontalListSortingStrategy}>
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
@@ -1061,12 +1060,12 @@ export default function PurchaseOrderPage() {
|
||||
{visibleModalColumns.map((col) => {
|
||||
switch (col.key) {
|
||||
case "item_code":
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono max-w-[120px]"><span className="block truncate" title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono whitespace-nowrap"><span title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
case "item_name":
|
||||
return <TableCell key={col.key} className="text-[13px] max-w-[120px]"><span className="block truncate" title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] whitespace-nowrap"><span title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
case "supplier":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[150px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{(categoryOptions["supplier_code"] || []).find(o => o.code === row.supplier_code)?.label || row.supplier_code || "-"}</span>
|
||||
) : (
|
||||
@@ -1076,7 +1075,7 @@ export default function PurchaseOrderPage() {
|
||||
updateDetailRow(idx, "supplier_code", v);
|
||||
updateDetailRow(idx, "supplier_name", name);
|
||||
}}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["supplier_code"] || []).map(o => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1092,11 +1091,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px]">{row.unit}</TableCell>;
|
||||
case "order_qty":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[110px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.order_qty ? Number(row.order_qty).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1106,11 +1105,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono">{row.remain_qty ? Number(row.remain_qty).toLocaleString() : "0"}</TableCell>;
|
||||
case "unit_price":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[120px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.unit_price ? Number(row.unit_price).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1118,21 +1117,21 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono font-semibold">{row.amount ? Number(row.amount).toLocaleString() : ""}</TableCell>;
|
||||
case "due_date":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[160px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.due_date}</span>
|
||||
) : (
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs" />
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
case "memo":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[140px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.memo}</span>
|
||||
) : (
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs" />
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
|
||||
@@ -1320,44 +1320,44 @@ export default function SalesOrderPage() {
|
||||
<Table noWrapper className="min-w-max w-full">
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
<TableHead className="w-10 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="w-28 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="w-24 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="w-24 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
<TableHead className="min-w-[40px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="min-w-[120px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="min-w-[150px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="min-w-[110px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="min-w-[100px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="min-w-[90px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="min-w-[120px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="min-w-[160px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="min-w-[110px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{detailRows.map((row, idx) => (
|
||||
<TableRow key={row._id || idx}>
|
||||
<TableCell className="text-center font-mono text-[13px] text-muted-foreground/50">{idx + 1}</TableCell>
|
||||
<TableCell className="max-w-[112px]">
|
||||
<span className="block truncate font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[128px]">
|
||||
<span className="block truncate text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.material}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.material}</TableCell>
|
||||
<TableCell>
|
||||
<Input
|
||||
value={row.packing_material || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "packing_material", e.target.value)}
|
||||
placeholder="포장재"
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Select value={row.unit || ""} onValueChange={(v) => updateDetailRow(idx, "unit", v)}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["item_unit"] || []).map((o) => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1371,7 +1371,7 @@ export default function SalesOrderPage() {
|
||||
min="1"
|
||||
value={row.qty || "1"}
|
||||
onChange={(e) => updateDetailRow(idx, "qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1380,7 +1380,7 @@ export default function SalesOrderPage() {
|
||||
min="0"
|
||||
value={row.pack_qty || "0"}
|
||||
onChange={(e) => updateDetailRow(idx, "pack_qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1388,10 +1388,10 @@ export default function SalesOrderPage() {
|
||||
value={formatNumber(row.unit_price || "")}
|
||||
onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))}
|
||||
readOnly={!allowPriceEdit}
|
||||
className={cn("h-8 text-xs text-right font-mono w-20", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
className={cn("h-8 text-xs text-right font-mono w-full", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold">
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold whitespace-nowrap">
|
||||
{row.amount ? Number(row.amount).toLocaleString() : "0"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1399,7 +1399,7 @@ export default function SalesOrderPage() {
|
||||
type="date"
|
||||
value={row.due_date || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
|
||||
@@ -88,18 +88,18 @@ const GRID_COLUMNS_CONFIG = [
|
||||
];
|
||||
|
||||
const MODAL_DETAIL_COLUMNS = [
|
||||
{ key: "item_code", label: "품번", width: "w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "w-[120px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "w-[60px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "w-[120px]" },
|
||||
{ key: "item_code", label: "품번", width: "min-w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "min-w-[150px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "min-w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "min-w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "min-w-[90px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "min-w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "min-w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "min-w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "min-w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "min-w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "min-w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "min-w-[120px]" },
|
||||
];
|
||||
|
||||
const MODAL_COL_ORDER_KEY = "purchase_order_modal_col_order_c16";
|
||||
@@ -237,7 +237,7 @@ export default function PurchaseOrderPage() {
|
||||
);
|
||||
try {
|
||||
const suppRes = await apiClient.post(`/table-management/tables/supplier_mng/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const supps = suppRes.data?.data?.data || suppRes.data?.data?.rows || [];
|
||||
optMap["supplier_code"] = supps.map((s: any) => ({
|
||||
@@ -247,7 +247,7 @@ export default function PurchaseOrderPage() {
|
||||
} catch { /* skip */ }
|
||||
try {
|
||||
const userRes = await apiClient.post(`/table-management/tables/user_info/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
optMap["manager"] = users.map((u: any) => ({
|
||||
@@ -293,7 +293,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
|
||||
const res = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: detailFilters.length > 0 ? { enabled: true, filters: detailFilters } : undefined,
|
||||
autoFilter: true,
|
||||
sort: { columnName: "purchase_no", order: "desc" },
|
||||
@@ -538,7 +538,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// 품목 검색
|
||||
// 품목 검색 (수주관리와 동일한 서버 페이징 방식)
|
||||
const searchItems = async (page?: number, size?: number) => {
|
||||
const p = page ?? itemPage;
|
||||
const s = size ?? itemPageSize;
|
||||
@@ -548,25 +548,24 @@ export default function PurchaseOrderPage() {
|
||||
if (itemSearchKeyword) {
|
||||
filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword });
|
||||
}
|
||||
// 관리품목 필터를 서버 쿼리에 포함 (코드 + 라벨 양쪽 대응)
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
const divValues = [itemSearchDivision];
|
||||
if (divLabel) divValues.push(divLabel);
|
||||
filters.push({ columnName: "division", operator: "in", value: divValues });
|
||||
}
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: p, size: s,
|
||||
dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
|
||||
autoFilter: true,
|
||||
});
|
||||
const resData = res.data?.data;
|
||||
let allRows = resData?.data || resData?.rows || [];
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
allRows = allRows.filter((item: any) => {
|
||||
const div = item.division || "";
|
||||
return div.includes(itemSearchDivision) || (divLabel && div.includes(divLabel));
|
||||
});
|
||||
}
|
||||
const total = allRows.length;
|
||||
const start = (p - 1) * s;
|
||||
setItemSearchResults(allRows.slice(start, start + s));
|
||||
setItemTotal(total);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(total / s)));
|
||||
const rows = resData?.data || resData?.rows || [];
|
||||
const serverTotal = resData?.total || resData?.totalCount || rows.length;
|
||||
setItemSearchResults(rows);
|
||||
setItemTotal(serverTotal);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(serverTotal / s)));
|
||||
} catch { /* skip */ } finally {
|
||||
setItemSearchLoading(false);
|
||||
}
|
||||
@@ -608,7 +607,7 @@ export default function PurchaseOrderPage() {
|
||||
try {
|
||||
const itemIds = selected.map((item) => item.item_number || item.id);
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: {
|
||||
enabled: true,
|
||||
filters: [
|
||||
@@ -671,7 +670,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "item_number", operator: "in", value: itemCodes }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
@@ -693,7 +692,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [
|
||||
{ columnName: "supplier_id", operator: "equals", value: supplierCode },
|
||||
{ columnName: "item_id", operator: "in", value: itemCodes },
|
||||
@@ -1037,7 +1036,7 @@ export default function PurchaseOrderPage() {
|
||||
) : (
|
||||
<div className="border rounded-lg overflow-x-auto">
|
||||
<DndContext sensors={modalSensors} collisionDetection={closestCenter} onDragEnd={handleModalDragEnd}>
|
||||
<Table className="table-fixed">
|
||||
<Table>
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<SortableContext items={visibleModalColumns.map((c) => c.key)} strategy={horizontalListSortingStrategy}>
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
@@ -1061,12 +1060,12 @@ export default function PurchaseOrderPage() {
|
||||
{visibleModalColumns.map((col) => {
|
||||
switch (col.key) {
|
||||
case "item_code":
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono max-w-[120px]"><span className="block truncate" title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono whitespace-nowrap"><span title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
case "item_name":
|
||||
return <TableCell key={col.key} className="text-[13px] max-w-[120px]"><span className="block truncate" title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] whitespace-nowrap"><span title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
case "supplier":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[150px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{(categoryOptions["supplier_code"] || []).find(o => o.code === row.supplier_code)?.label || row.supplier_code || "-"}</span>
|
||||
) : (
|
||||
@@ -1076,7 +1075,7 @@ export default function PurchaseOrderPage() {
|
||||
updateDetailRow(idx, "supplier_code", v);
|
||||
updateDetailRow(idx, "supplier_name", name);
|
||||
}}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["supplier_code"] || []).map(o => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1092,11 +1091,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px]">{row.unit}</TableCell>;
|
||||
case "order_qty":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[110px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.order_qty ? Number(row.order_qty).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1106,11 +1105,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono">{row.remain_qty ? Number(row.remain_qty).toLocaleString() : "0"}</TableCell>;
|
||||
case "unit_price":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[120px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.unit_price ? Number(row.unit_price).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1118,21 +1117,21 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono font-semibold">{row.amount ? Number(row.amount).toLocaleString() : ""}</TableCell>;
|
||||
case "due_date":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[160px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.due_date}</span>
|
||||
) : (
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs" />
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
case "memo":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[140px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.memo}</span>
|
||||
) : (
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs" />
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
|
||||
@@ -517,42 +517,44 @@ export default function SalesOrderPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// 삭제 (마스터 + 디테일)
|
||||
// 삭제 (선택한 디테일 삭제 → 디테일 0건인 마스터 자동 삭제)
|
||||
const handleDelete = async () => {
|
||||
if (checkedIds.length === 0) { toast.error("삭제할 수주를 선택해주세요."); return; }
|
||||
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
|
||||
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
|
||||
const ok = await confirm(`${orderNos.length}건의 수주를 삭제하시겠습니까?`, {
|
||||
if (checkedIds.length === 0) { toast.error("삭제할 항목을 선택해주세요."); return; }
|
||||
const ok = await confirm(`${checkedIds.length}건의 수주 항목을 삭제하시겠습니까?`, {
|
||||
description: "삭제된 데이터는 복구할 수 없습니다.",
|
||||
variant: "destructive",
|
||||
confirmText: "삭제",
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
// 1. 선택한 디테일 삭제
|
||||
await apiClient.delete(`/table-management/tables/${DETAIL_TABLE}/delete`, {
|
||||
data: checkedIds.map((id) => ({ id })),
|
||||
});
|
||||
|
||||
// 2. 영향받는 수주번호의 잔여 디테일 확인 → 0건이면 마스터도 삭제
|
||||
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
|
||||
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
|
||||
for (const orderNo of orderNos) {
|
||||
// 디테일 삭제
|
||||
const detailRes = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
|
||||
page: 1, size: 9999,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
const details = detailRes.data?.data?.data || detailRes.data?.data?.rows || [];
|
||||
if (details.length > 0) {
|
||||
await apiClient.delete(`/table-management/tables/${DETAIL_TABLE}/delete`, {
|
||||
data: details.map((d: any) => ({ id: d.id })),
|
||||
});
|
||||
}
|
||||
// 마스터 삭제
|
||||
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
|
||||
const remainRes = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
|
||||
page: 1, size: 1,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
|
||||
if (masters.length > 0) {
|
||||
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
|
||||
data: masters.map((m: any) => ({ id: m.id })),
|
||||
const remaining = remainRes.data?.data?.data || remainRes.data?.data?.rows || [];
|
||||
if (remaining.length === 0) {
|
||||
// 디테일 0건 → 마스터 삭제
|
||||
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
|
||||
page: 1, size: 1,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
|
||||
if (masters.length > 0) {
|
||||
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
|
||||
data: masters.map((m: any) => ({ id: m.id })),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
toast.success("삭제되었습니다.");
|
||||
@@ -622,6 +624,15 @@ export default function SalesOrderPage() {
|
||||
const filters: any[] = [];
|
||||
if (itemSearchKeyword) filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword });
|
||||
|
||||
// 관리품목 필터를 서버 쿼리에 포함 (코드 + 라벨 양쪽 대응)
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
// 코드 또는 라벨이 저장된 경우 모두 조회하기 위해 in 연산자 사용
|
||||
const divValues = [itemSearchDivision];
|
||||
if (divLabel) divValues.push(divLabel);
|
||||
filters.push({ columnName: "division", operator: "in", value: divValues });
|
||||
}
|
||||
|
||||
// 거래처우선 단가방식일 때 거래처에 연결된 품목만 필터링
|
||||
const isCustomerPrice = masterForm.price_mode === "CAT_MM0BV3OS_41DX" || masterForm.price_mode === "CAT_MLKG7D8K_N8SI";
|
||||
const partnerId = masterForm.partner_id;
|
||||
@@ -630,7 +641,7 @@ export default function SalesOrderPage() {
|
||||
if (isCustomerPrice && partnerId) {
|
||||
try {
|
||||
const mappingRes = await apiClient.post(`/table-management/tables/customer_item_mapping/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "customer_id", operator: "equals", value: partnerId }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
@@ -640,31 +651,22 @@ export default function SalesOrderPage() {
|
||||
}
|
||||
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: p, size: s,
|
||||
dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
|
||||
autoFilter: true,
|
||||
});
|
||||
const resData = res.data?.data;
|
||||
let allRows = resData?.data || resData?.rows || [];
|
||||
let rows = resData?.data || resData?.rows || [];
|
||||
const serverTotal = resData?.total || resData?.totalCount || rows.length;
|
||||
|
||||
// 거래처우선일 때 연결된 품목만 표시
|
||||
// 거래처우선일 때 연결된 품목만 표시 (클라이언트 필터)
|
||||
if (customerItemIds) {
|
||||
allRows = allRows.filter((item: any) => customerItemIds!.has(item.item_number) || customerItemIds!.has(item.id));
|
||||
rows = rows.filter((item: any) => customerItemIds!.has(item.item_number) || customerItemIds!.has(item.id));
|
||||
}
|
||||
|
||||
// 관리품목 필터 (코드/라벨 혼재 대응)
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
allRows = allRows.filter((item: any) => {
|
||||
const div = item.division || "";
|
||||
return div.includes(itemSearchDivision) || (divLabel && div.includes(divLabel));
|
||||
});
|
||||
}
|
||||
const total = allRows.length;
|
||||
const start = (p - 1) * s;
|
||||
setItemSearchResults(allRows.slice(start, start + s));
|
||||
setItemTotal(total);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(total / s)));
|
||||
setItemSearchResults(rows);
|
||||
setItemTotal(serverTotal);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(serverTotal / s)));
|
||||
} catch { /* skip */ } finally {
|
||||
setItemSearchLoading(false);
|
||||
}
|
||||
@@ -1318,44 +1320,44 @@ export default function SalesOrderPage() {
|
||||
<Table noWrapper className="min-w-max w-full">
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
<TableHead className="w-10 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="w-28 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="w-24 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="w-24 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
<TableHead className="min-w-[40px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="min-w-[120px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="min-w-[150px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="min-w-[110px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="min-w-[100px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="min-w-[90px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="min-w-[120px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="min-w-[160px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="min-w-[110px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{detailRows.map((row, idx) => (
|
||||
<TableRow key={row._id || idx}>
|
||||
<TableCell className="text-center font-mono text-[13px] text-muted-foreground/50">{idx + 1}</TableCell>
|
||||
<TableCell className="max-w-[112px]">
|
||||
<span className="block truncate font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[128px]">
|
||||
<span className="block truncate text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.material}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.material}</TableCell>
|
||||
<TableCell>
|
||||
<Input
|
||||
value={row.packing_material || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "packing_material", e.target.value)}
|
||||
placeholder="포장재"
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Select value={row.unit || ""} onValueChange={(v) => updateDetailRow(idx, "unit", v)}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["item_unit"] || []).map((o) => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1369,7 +1371,7 @@ export default function SalesOrderPage() {
|
||||
min="1"
|
||||
value={row.qty || "1"}
|
||||
onChange={(e) => updateDetailRow(idx, "qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1378,7 +1380,7 @@ export default function SalesOrderPage() {
|
||||
min="0"
|
||||
value={row.pack_qty || "0"}
|
||||
onChange={(e) => updateDetailRow(idx, "pack_qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1386,10 +1388,10 @@ export default function SalesOrderPage() {
|
||||
value={formatNumber(row.unit_price || "")}
|
||||
onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))}
|
||||
readOnly={!allowPriceEdit}
|
||||
className={cn("h-8 text-xs text-right font-mono w-20", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
className={cn("h-8 text-xs text-right font-mono w-full", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold">
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold whitespace-nowrap">
|
||||
{row.amount ? Number(row.amount).toLocaleString() : "0"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1397,7 +1399,7 @@ export default function SalesOrderPage() {
|
||||
type="date"
|
||||
value={row.due_date || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
@@ -1472,15 +1474,7 @@ export default function SalesOrderPage() {
|
||||
onKeyDown={(e) => e.key === "Enter" && triggerNewSearch()}
|
||||
className="h-9 flex-1"
|
||||
/>
|
||||
<Select value={itemSearchDivision} onValueChange={setItemSearchDivision}>
|
||||
<SelectTrigger className="h-9 w-[130px]"><SelectValue placeholder="관리품목" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">전체</SelectItem>
|
||||
{(categoryOptions["item_division"] || []).map((o) => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="h-9 px-3 flex items-center rounded-md border border-input bg-muted text-xs font-medium text-muted-foreground whitespace-nowrap">영업관리</div>
|
||||
<Button size="sm" onClick={triggerNewSearch} disabled={itemSearchLoading} className="h-9">
|
||||
{itemSearchLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <><Search className="w-4 h-4 mr-1" />조회</>}
|
||||
</Button>
|
||||
|
||||
@@ -88,18 +88,18 @@ const GRID_COLUMNS_CONFIG = [
|
||||
];
|
||||
|
||||
const MODAL_DETAIL_COLUMNS = [
|
||||
{ key: "item_code", label: "품번", width: "w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "w-[120px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "w-[60px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "w-[120px]" },
|
||||
{ key: "item_code", label: "품번", width: "min-w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "min-w-[150px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "min-w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "min-w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "min-w-[90px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "min-w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "min-w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "min-w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "min-w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "min-w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "min-w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "min-w-[120px]" },
|
||||
];
|
||||
|
||||
const MODAL_COL_ORDER_KEY = "purchase_order_modal_col_order_c16";
|
||||
@@ -237,7 +237,7 @@ export default function PurchaseOrderPage() {
|
||||
);
|
||||
try {
|
||||
const suppRes = await apiClient.post(`/table-management/tables/supplier_mng/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const supps = suppRes.data?.data?.data || suppRes.data?.data?.rows || [];
|
||||
optMap["supplier_code"] = supps.map((s: any) => ({
|
||||
@@ -247,7 +247,7 @@ export default function PurchaseOrderPage() {
|
||||
} catch { /* skip */ }
|
||||
try {
|
||||
const userRes = await apiClient.post(`/table-management/tables/user_info/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
optMap["manager"] = users.map((u: any) => ({
|
||||
@@ -293,7 +293,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
|
||||
const res = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: detailFilters.length > 0 ? { enabled: true, filters: detailFilters } : undefined,
|
||||
autoFilter: true,
|
||||
sort: { columnName: "purchase_no", order: "desc" },
|
||||
@@ -538,7 +538,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// 품목 검색
|
||||
// 품목 검색 (수주관리와 동일한 서버 페이징 방식)
|
||||
const searchItems = async (page?: number, size?: number) => {
|
||||
const p = page ?? itemPage;
|
||||
const s = size ?? itemPageSize;
|
||||
@@ -548,25 +548,24 @@ export default function PurchaseOrderPage() {
|
||||
if (itemSearchKeyword) {
|
||||
filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword });
|
||||
}
|
||||
// 관리품목 필터를 서버 쿼리에 포함 (코드 + 라벨 양쪽 대응)
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
const divValues = [itemSearchDivision];
|
||||
if (divLabel) divValues.push(divLabel);
|
||||
filters.push({ columnName: "division", operator: "in", value: divValues });
|
||||
}
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: p, size: s,
|
||||
dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
|
||||
autoFilter: true,
|
||||
});
|
||||
const resData = res.data?.data;
|
||||
let allRows = resData?.data || resData?.rows || [];
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
allRows = allRows.filter((item: any) => {
|
||||
const div = item.division || "";
|
||||
return div.includes(itemSearchDivision) || (divLabel && div.includes(divLabel));
|
||||
});
|
||||
}
|
||||
const total = allRows.length;
|
||||
const start = (p - 1) * s;
|
||||
setItemSearchResults(allRows.slice(start, start + s));
|
||||
setItemTotal(total);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(total / s)));
|
||||
const rows = resData?.data || resData?.rows || [];
|
||||
const serverTotal = resData?.total || resData?.totalCount || rows.length;
|
||||
setItemSearchResults(rows);
|
||||
setItemTotal(serverTotal);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(serverTotal / s)));
|
||||
} catch { /* skip */ } finally {
|
||||
setItemSearchLoading(false);
|
||||
}
|
||||
@@ -608,7 +607,7 @@ export default function PurchaseOrderPage() {
|
||||
try {
|
||||
const itemIds = selected.map((item) => item.item_number || item.id);
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: {
|
||||
enabled: true,
|
||||
filters: [
|
||||
@@ -671,7 +670,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "item_number", operator: "in", value: itemCodes }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
@@ -693,7 +692,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [
|
||||
{ columnName: "supplier_id", operator: "equals", value: supplierCode },
|
||||
{ columnName: "item_id", operator: "in", value: itemCodes },
|
||||
@@ -1037,7 +1036,7 @@ export default function PurchaseOrderPage() {
|
||||
) : (
|
||||
<div className="border rounded-lg overflow-x-auto">
|
||||
<DndContext sensors={modalSensors} collisionDetection={closestCenter} onDragEnd={handleModalDragEnd}>
|
||||
<Table className="table-fixed">
|
||||
<Table>
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<SortableContext items={visibleModalColumns.map((c) => c.key)} strategy={horizontalListSortingStrategy}>
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
@@ -1061,12 +1060,12 @@ export default function PurchaseOrderPage() {
|
||||
{visibleModalColumns.map((col) => {
|
||||
switch (col.key) {
|
||||
case "item_code":
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono max-w-[120px]"><span className="block truncate" title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono whitespace-nowrap"><span title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
case "item_name":
|
||||
return <TableCell key={col.key} className="text-[13px] max-w-[120px]"><span className="block truncate" title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] whitespace-nowrap"><span title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
case "supplier":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[150px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{(categoryOptions["supplier_code"] || []).find(o => o.code === row.supplier_code)?.label || row.supplier_code || "-"}</span>
|
||||
) : (
|
||||
@@ -1076,7 +1075,7 @@ export default function PurchaseOrderPage() {
|
||||
updateDetailRow(idx, "supplier_code", v);
|
||||
updateDetailRow(idx, "supplier_name", name);
|
||||
}}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["supplier_code"] || []).map(o => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1092,11 +1091,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px]">{row.unit}</TableCell>;
|
||||
case "order_qty":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[110px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.order_qty ? Number(row.order_qty).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1106,11 +1105,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono">{row.remain_qty ? Number(row.remain_qty).toLocaleString() : "0"}</TableCell>;
|
||||
case "unit_price":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[120px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.unit_price ? Number(row.unit_price).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1118,21 +1117,21 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono font-semibold">{row.amount ? Number(row.amount).toLocaleString() : ""}</TableCell>;
|
||||
case "due_date":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[160px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.due_date}</span>
|
||||
) : (
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs" />
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
case "memo":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[140px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.memo}</span>
|
||||
) : (
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs" />
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
|
||||
@@ -1320,44 +1320,44 @@ export default function SalesOrderPage() {
|
||||
<Table noWrapper className="min-w-max w-full">
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
<TableHead className="w-10 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="w-28 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="w-24 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="w-24 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
<TableHead className="min-w-[40px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="min-w-[120px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="min-w-[150px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="min-w-[110px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="min-w-[100px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="min-w-[90px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="min-w-[120px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="min-w-[160px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="min-w-[110px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{detailRows.map((row, idx) => (
|
||||
<TableRow key={row._id || idx}>
|
||||
<TableCell className="text-center font-mono text-[13px] text-muted-foreground/50">{idx + 1}</TableCell>
|
||||
<TableCell className="max-w-[112px]">
|
||||
<span className="block truncate font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[128px]">
|
||||
<span className="block truncate text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.material}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.material}</TableCell>
|
||||
<TableCell>
|
||||
<Input
|
||||
value={row.packing_material || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "packing_material", e.target.value)}
|
||||
placeholder="포장재"
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Select value={row.unit || ""} onValueChange={(v) => updateDetailRow(idx, "unit", v)}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["item_unit"] || []).map((o) => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1371,7 +1371,7 @@ export default function SalesOrderPage() {
|
||||
min="1"
|
||||
value={row.qty || "1"}
|
||||
onChange={(e) => updateDetailRow(idx, "qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1380,7 +1380,7 @@ export default function SalesOrderPage() {
|
||||
min="0"
|
||||
value={row.pack_qty || "0"}
|
||||
onChange={(e) => updateDetailRow(idx, "pack_qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1388,10 +1388,10 @@ export default function SalesOrderPage() {
|
||||
value={formatNumber(row.unit_price || "")}
|
||||
onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))}
|
||||
readOnly={!allowPriceEdit}
|
||||
className={cn("h-8 text-xs text-right font-mono w-20", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
className={cn("h-8 text-xs text-right font-mono w-full", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold">
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold whitespace-nowrap">
|
||||
{row.amount ? Number(row.amount).toLocaleString() : "0"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1399,7 +1399,7 @@ export default function SalesOrderPage() {
|
||||
type="date"
|
||||
value={row.due_date || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
|
||||
@@ -88,18 +88,18 @@ const GRID_COLUMNS_CONFIG = [
|
||||
];
|
||||
|
||||
const MODAL_DETAIL_COLUMNS = [
|
||||
{ key: "item_code", label: "품번", width: "w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "w-[120px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "w-[60px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "w-[120px]" },
|
||||
{ key: "item_code", label: "품번", width: "min-w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "min-w-[150px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "min-w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "min-w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "min-w-[90px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "min-w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "min-w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "min-w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "min-w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "min-w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "min-w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "min-w-[120px]" },
|
||||
];
|
||||
|
||||
const MODAL_COL_ORDER_KEY = "purchase_order_modal_col_order_c16";
|
||||
@@ -237,7 +237,7 @@ export default function PurchaseOrderPage() {
|
||||
);
|
||||
try {
|
||||
const suppRes = await apiClient.post(`/table-management/tables/supplier_mng/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const supps = suppRes.data?.data?.data || suppRes.data?.data?.rows || [];
|
||||
optMap["supplier_code"] = supps.map((s: any) => ({
|
||||
@@ -247,7 +247,7 @@ export default function PurchaseOrderPage() {
|
||||
} catch { /* skip */ }
|
||||
try {
|
||||
const userRes = await apiClient.post(`/table-management/tables/user_info/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
optMap["manager"] = users.map((u: any) => ({
|
||||
@@ -293,7 +293,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
|
||||
const res = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: detailFilters.length > 0 ? { enabled: true, filters: detailFilters } : undefined,
|
||||
autoFilter: true,
|
||||
sort: { columnName: "purchase_no", order: "desc" },
|
||||
@@ -538,7 +538,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// 품목 검색
|
||||
// 품목 검색 (수주관리와 동일한 서버 페이징 방식)
|
||||
const searchItems = async (page?: number, size?: number) => {
|
||||
const p = page ?? itemPage;
|
||||
const s = size ?? itemPageSize;
|
||||
@@ -548,25 +548,24 @@ export default function PurchaseOrderPage() {
|
||||
if (itemSearchKeyword) {
|
||||
filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword });
|
||||
}
|
||||
// 관리품목 필터를 서버 쿼리에 포함 (코드 + 라벨 양쪽 대응)
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
const divValues = [itemSearchDivision];
|
||||
if (divLabel) divValues.push(divLabel);
|
||||
filters.push({ columnName: "division", operator: "in", value: divValues });
|
||||
}
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: p, size: s,
|
||||
dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
|
||||
autoFilter: true,
|
||||
});
|
||||
const resData = res.data?.data;
|
||||
let allRows = resData?.data || resData?.rows || [];
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
allRows = allRows.filter((item: any) => {
|
||||
const div = item.division || "";
|
||||
return div.includes(itemSearchDivision) || (divLabel && div.includes(divLabel));
|
||||
});
|
||||
}
|
||||
const total = allRows.length;
|
||||
const start = (p - 1) * s;
|
||||
setItemSearchResults(allRows.slice(start, start + s));
|
||||
setItemTotal(total);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(total / s)));
|
||||
const rows = resData?.data || resData?.rows || [];
|
||||
const serverTotal = resData?.total || resData?.totalCount || rows.length;
|
||||
setItemSearchResults(rows);
|
||||
setItemTotal(serverTotal);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(serverTotal / s)));
|
||||
} catch { /* skip */ } finally {
|
||||
setItemSearchLoading(false);
|
||||
}
|
||||
@@ -608,7 +607,7 @@ export default function PurchaseOrderPage() {
|
||||
try {
|
||||
const itemIds = selected.map((item) => item.item_number || item.id);
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: {
|
||||
enabled: true,
|
||||
filters: [
|
||||
@@ -671,7 +670,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "item_number", operator: "in", value: itemCodes }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
@@ -693,7 +692,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [
|
||||
{ columnName: "supplier_id", operator: "equals", value: supplierCode },
|
||||
{ columnName: "item_id", operator: "in", value: itemCodes },
|
||||
@@ -1037,7 +1036,7 @@ export default function PurchaseOrderPage() {
|
||||
) : (
|
||||
<div className="border rounded-lg overflow-x-auto">
|
||||
<DndContext sensors={modalSensors} collisionDetection={closestCenter} onDragEnd={handleModalDragEnd}>
|
||||
<Table className="table-fixed">
|
||||
<Table>
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<SortableContext items={visibleModalColumns.map((c) => c.key)} strategy={horizontalListSortingStrategy}>
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
@@ -1061,12 +1060,12 @@ export default function PurchaseOrderPage() {
|
||||
{visibleModalColumns.map((col) => {
|
||||
switch (col.key) {
|
||||
case "item_code":
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono max-w-[120px]"><span className="block truncate" title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono whitespace-nowrap"><span title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
case "item_name":
|
||||
return <TableCell key={col.key} className="text-[13px] max-w-[120px]"><span className="block truncate" title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] whitespace-nowrap"><span title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
case "supplier":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[150px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{(categoryOptions["supplier_code"] || []).find(o => o.code === row.supplier_code)?.label || row.supplier_code || "-"}</span>
|
||||
) : (
|
||||
@@ -1076,7 +1075,7 @@ export default function PurchaseOrderPage() {
|
||||
updateDetailRow(idx, "supplier_code", v);
|
||||
updateDetailRow(idx, "supplier_name", name);
|
||||
}}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["supplier_code"] || []).map(o => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1092,11 +1091,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px]">{row.unit}</TableCell>;
|
||||
case "order_qty":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[110px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.order_qty ? Number(row.order_qty).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1106,11 +1105,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono">{row.remain_qty ? Number(row.remain_qty).toLocaleString() : "0"}</TableCell>;
|
||||
case "unit_price":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[120px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.unit_price ? Number(row.unit_price).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1118,21 +1117,21 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono font-semibold">{row.amount ? Number(row.amount).toLocaleString() : ""}</TableCell>;
|
||||
case "due_date":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[160px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.due_date}</span>
|
||||
) : (
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs" />
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
case "memo":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[140px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.memo}</span>
|
||||
) : (
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs" />
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
|
||||
@@ -1320,44 +1320,44 @@ export default function SalesOrderPage() {
|
||||
<Table noWrapper className="min-w-max w-full">
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
<TableHead className="w-10 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="w-28 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="w-24 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="w-24 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
<TableHead className="min-w-[40px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="min-w-[120px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="min-w-[150px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="min-w-[110px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="min-w-[100px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="min-w-[90px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="min-w-[120px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="min-w-[160px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="min-w-[110px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{detailRows.map((row, idx) => (
|
||||
<TableRow key={row._id || idx}>
|
||||
<TableCell className="text-center font-mono text-[13px] text-muted-foreground/50">{idx + 1}</TableCell>
|
||||
<TableCell className="max-w-[112px]">
|
||||
<span className="block truncate font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[128px]">
|
||||
<span className="block truncate text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.material}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.material}</TableCell>
|
||||
<TableCell>
|
||||
<Input
|
||||
value={row.packing_material || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "packing_material", e.target.value)}
|
||||
placeholder="포장재"
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Select value={row.unit || ""} onValueChange={(v) => updateDetailRow(idx, "unit", v)}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["item_unit"] || []).map((o) => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1371,7 +1371,7 @@ export default function SalesOrderPage() {
|
||||
min="1"
|
||||
value={row.qty || "1"}
|
||||
onChange={(e) => updateDetailRow(idx, "qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1380,7 +1380,7 @@ export default function SalesOrderPage() {
|
||||
min="0"
|
||||
value={row.pack_qty || "0"}
|
||||
onChange={(e) => updateDetailRow(idx, "pack_qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1388,10 +1388,10 @@ export default function SalesOrderPage() {
|
||||
value={formatNumber(row.unit_price || "")}
|
||||
onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))}
|
||||
readOnly={!allowPriceEdit}
|
||||
className={cn("h-8 text-xs text-right font-mono w-20", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
className={cn("h-8 text-xs text-right font-mono w-full", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold">
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold whitespace-nowrap">
|
||||
{row.amount ? Number(row.amount).toLocaleString() : "0"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1399,7 +1399,7 @@ export default function SalesOrderPage() {
|
||||
type="date"
|
||||
value={row.due_date || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
|
||||
@@ -88,18 +88,18 @@ const GRID_COLUMNS_CONFIG = [
|
||||
];
|
||||
|
||||
const MODAL_DETAIL_COLUMNS = [
|
||||
{ key: "item_code", label: "품번", width: "w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "w-[120px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "w-[60px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "w-[120px]" },
|
||||
{ key: "item_code", label: "품번", width: "min-w-[120px]" },
|
||||
{ key: "item_name", label: "품명", width: "min-w-[150px]" },
|
||||
{ key: "supplier", label: "공급업체", width: "min-w-[150px]" },
|
||||
{ key: "spec", label: "규격", width: "min-w-[80px]" },
|
||||
{ key: "unit", label: "단위", width: "min-w-[90px]" },
|
||||
{ key: "order_qty", label: "발주수량", width: "min-w-[90px]" },
|
||||
{ key: "received_qty", label: "입고수량", width: "min-w-[90px]" },
|
||||
{ key: "remain_qty", label: "잔량", width: "min-w-[80px]" },
|
||||
{ key: "unit_price", label: "단가", width: "min-w-[100px]" },
|
||||
{ key: "amount", label: "금액", width: "min-w-[100px]" },
|
||||
{ key: "due_date", label: "납기일", width: "min-w-[160px]" },
|
||||
{ key: "memo", label: "메모", width: "min-w-[120px]" },
|
||||
];
|
||||
|
||||
const MODAL_COL_ORDER_KEY = "purchase_order_modal_col_order_c16";
|
||||
@@ -237,7 +237,7 @@ export default function PurchaseOrderPage() {
|
||||
);
|
||||
try {
|
||||
const suppRes = await apiClient.post(`/table-management/tables/supplier_mng/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const supps = suppRes.data?.data?.data || suppRes.data?.data?.rows || [];
|
||||
optMap["supplier_code"] = supps.map((s: any) => ({
|
||||
@@ -247,7 +247,7 @@ export default function PurchaseOrderPage() {
|
||||
} catch { /* skip */ }
|
||||
try {
|
||||
const userRes = await apiClient.post(`/table-management/tables/user_info/data`, {
|
||||
page: 1, size: 500, autoFilter: true,
|
||||
page: 1, size: 5000, autoFilter: true,
|
||||
});
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
optMap["manager"] = users.map((u: any) => ({
|
||||
@@ -293,7 +293,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
|
||||
const res = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: detailFilters.length > 0 ? { enabled: true, filters: detailFilters } : undefined,
|
||||
autoFilter: true,
|
||||
sort: { columnName: "purchase_no", order: "desc" },
|
||||
@@ -538,7 +538,7 @@ export default function PurchaseOrderPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// 품목 검색
|
||||
// 품목 검색 (수주관리와 동일한 서버 페이징 방식)
|
||||
const searchItems = async (page?: number, size?: number) => {
|
||||
const p = page ?? itemPage;
|
||||
const s = size ?? itemPageSize;
|
||||
@@ -548,25 +548,24 @@ export default function PurchaseOrderPage() {
|
||||
if (itemSearchKeyword) {
|
||||
filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword });
|
||||
}
|
||||
// 관리품목 필터를 서버 쿼리에 포함 (코드 + 라벨 양쪽 대응)
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
const divValues = [itemSearchDivision];
|
||||
if (divLabel) divValues.push(divLabel);
|
||||
filters.push({ columnName: "division", operator: "in", value: divValues });
|
||||
}
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: p, size: s,
|
||||
dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
|
||||
autoFilter: true,
|
||||
});
|
||||
const resData = res.data?.data;
|
||||
let allRows = resData?.data || resData?.rows || [];
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
allRows = allRows.filter((item: any) => {
|
||||
const div = item.division || "";
|
||||
return div.includes(itemSearchDivision) || (divLabel && div.includes(divLabel));
|
||||
});
|
||||
}
|
||||
const total = allRows.length;
|
||||
const start = (p - 1) * s;
|
||||
setItemSearchResults(allRows.slice(start, start + s));
|
||||
setItemTotal(total);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(total / s)));
|
||||
const rows = resData?.data || resData?.rows || [];
|
||||
const serverTotal = resData?.total || resData?.totalCount || rows.length;
|
||||
setItemSearchResults(rows);
|
||||
setItemTotal(serverTotal);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(serverTotal / s)));
|
||||
} catch { /* skip */ } finally {
|
||||
setItemSearchLoading(false);
|
||||
}
|
||||
@@ -608,7 +607,7 @@ export default function PurchaseOrderPage() {
|
||||
try {
|
||||
const itemIds = selected.map((item) => item.item_number || item.id);
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: {
|
||||
enabled: true,
|
||||
filters: [
|
||||
@@ -671,7 +670,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "item_number", operator: "in", value: itemCodes }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
@@ -693,7 +692,7 @@ export default function PurchaseOrderPage() {
|
||||
if (itemCodes.length === 0) return;
|
||||
try {
|
||||
const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [
|
||||
{ columnName: "supplier_id", operator: "equals", value: supplierCode },
|
||||
{ columnName: "item_id", operator: "in", value: itemCodes },
|
||||
@@ -1037,7 +1036,7 @@ export default function PurchaseOrderPage() {
|
||||
) : (
|
||||
<div className="border rounded-lg overflow-x-auto">
|
||||
<DndContext sensors={modalSensors} collisionDetection={closestCenter} onDragEnd={handleModalDragEnd}>
|
||||
<Table className="table-fixed">
|
||||
<Table>
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<SortableContext items={visibleModalColumns.map((c) => c.key)} strategy={horizontalListSortingStrategy}>
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
@@ -1061,12 +1060,12 @@ export default function PurchaseOrderPage() {
|
||||
{visibleModalColumns.map((col) => {
|
||||
switch (col.key) {
|
||||
case "item_code":
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono max-w-[120px]"><span className="block truncate" title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] font-mono whitespace-nowrap"><span title={row.item_code}>{row.item_code}</span></TableCell>;
|
||||
case "item_name":
|
||||
return <TableCell key={col.key} className="text-[13px] max-w-[120px]"><span className="block truncate" title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
return <TableCell key={col.key} className="text-[13px] whitespace-nowrap"><span title={row.item_name}>{row.item_name}</span></TableCell>;
|
||||
case "supplier":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[150px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{(categoryOptions["supplier_code"] || []).find(o => o.code === row.supplier_code)?.label || row.supplier_code || "-"}</span>
|
||||
) : (
|
||||
@@ -1076,7 +1075,7 @@ export default function PurchaseOrderPage() {
|
||||
updateDetailRow(idx, "supplier_code", v);
|
||||
updateDetailRow(idx, "supplier_name", name);
|
||||
}}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["supplier_code"] || []).map(o => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1092,11 +1091,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px]">{row.unit}</TableCell>;
|
||||
case "order_qty":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[110px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.order_qty ? Number(row.order_qty).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.order_qty || "")} onChange={(e) => updateDetailRow(idx, "order_qty", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1106,11 +1105,11 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono">{row.remain_qty ? Number(row.remain_qty).toLocaleString() : "0"}</TableCell>;
|
||||
case "unit_price":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[120px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs text-right font-mono block">{row.unit_price ? Number(row.unit_price).toLocaleString() : ""}</span>
|
||||
) : (
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono" />
|
||||
<Input value={formatNumber(row.unit_price || "")} onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))} className="h-8 text-xs text-right font-mono w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
@@ -1118,21 +1117,21 @@ export default function PurchaseOrderPage() {
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono font-semibold">{row.amount ? Number(row.amount).toLocaleString() : ""}</TableCell>;
|
||||
case "due_date":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[160px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.due_date}</span>
|
||||
) : (
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs" />
|
||||
<Input type="date" value={row.due_date || ""} onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
case "memo":
|
||||
return (
|
||||
<TableCell key={col.key}>
|
||||
<TableCell key={col.key} className="min-w-[140px]">
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{row.memo}</span>
|
||||
) : (
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs" />
|
||||
<Input value={row.memo || ""} onChange={(e) => updateDetailRow(idx, "memo", e.target.value)} className="h-8 text-xs w-full" />
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
|
||||
@@ -517,42 +517,44 @@ export default function SalesOrderPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// 삭제 (마스터 + 디테일)
|
||||
// 삭제 (선택한 디테일 삭제 → 디테일 0건인 마스터 자동 삭제)
|
||||
const handleDelete = async () => {
|
||||
if (checkedIds.length === 0) { toast.error("삭제할 수주를 선택해주세요."); return; }
|
||||
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
|
||||
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
|
||||
const ok = await confirm(`${orderNos.length}건의 수주를 삭제하시겠습니까?`, {
|
||||
if (checkedIds.length === 0) { toast.error("삭제할 항목을 선택해주세요."); return; }
|
||||
const ok = await confirm(`${checkedIds.length}건의 수주 항목을 삭제하시겠습니까?`, {
|
||||
description: "삭제된 데이터는 복구할 수 없습니다.",
|
||||
variant: "destructive",
|
||||
confirmText: "삭제",
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
// 1. 선택한 디테일 삭제
|
||||
await apiClient.delete(`/table-management/tables/${DETAIL_TABLE}/delete`, {
|
||||
data: checkedIds.map((id) => ({ id })),
|
||||
});
|
||||
|
||||
// 2. 영향받는 수주번호의 잔여 디테일 확인 → 0건이면 마스터도 삭제
|
||||
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
|
||||
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
|
||||
for (const orderNo of orderNos) {
|
||||
// 디테일 삭제
|
||||
const detailRes = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
|
||||
page: 1, size: 9999,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
const details = detailRes.data?.data?.data || detailRes.data?.data?.rows || [];
|
||||
if (details.length > 0) {
|
||||
await apiClient.delete(`/table-management/tables/${DETAIL_TABLE}/delete`, {
|
||||
data: details.map((d: any) => ({ id: d.id })),
|
||||
});
|
||||
}
|
||||
// 마스터 삭제
|
||||
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
|
||||
const remainRes = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
|
||||
page: 1, size: 1,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
|
||||
if (masters.length > 0) {
|
||||
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
|
||||
data: masters.map((m: any) => ({ id: m.id })),
|
||||
const remaining = remainRes.data?.data?.data || remainRes.data?.data?.rows || [];
|
||||
if (remaining.length === 0) {
|
||||
// 디테일 0건 → 마스터 삭제
|
||||
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
|
||||
page: 1, size: 1,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
|
||||
if (masters.length > 0) {
|
||||
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
|
||||
data: masters.map((m: any) => ({ id: m.id })),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
toast.success("삭제되었습니다.");
|
||||
@@ -622,6 +624,15 @@ export default function SalesOrderPage() {
|
||||
const filters: any[] = [];
|
||||
if (itemSearchKeyword) filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword });
|
||||
|
||||
// 관리품목 필터를 서버 쿼리에 포함 (코드 + 라벨 양쪽 대응)
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
// 코드 또는 라벨이 저장된 경우 모두 조회하기 위해 in 연산자 사용
|
||||
const divValues = [itemSearchDivision];
|
||||
if (divLabel) divValues.push(divLabel);
|
||||
filters.push({ columnName: "division", operator: "in", value: divValues });
|
||||
}
|
||||
|
||||
// 거래처우선 단가방식일 때 거래처에 연결된 품목만 필터링
|
||||
const isCustomerPrice = masterForm.price_mode === "CAT_MM0BV3OS_41DX" || masterForm.price_mode === "CAT_MLKG7D8K_N8SI";
|
||||
const partnerId = masterForm.partner_id;
|
||||
@@ -630,7 +641,7 @@ export default function SalesOrderPage() {
|
||||
if (isCustomerPrice && partnerId) {
|
||||
try {
|
||||
const mappingRes = await apiClient.post(`/table-management/tables/customer_item_mapping/data`, {
|
||||
page: 1, size: 500,
|
||||
page: 1, size: 5000,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "customer_id", operator: "equals", value: partnerId }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
@@ -640,31 +651,22 @@ export default function SalesOrderPage() {
|
||||
}
|
||||
|
||||
const res = await apiClient.post(`/table-management/tables/item_info/data`, {
|
||||
page: 1, size: 500,
|
||||
page: p, size: s,
|
||||
dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
|
||||
autoFilter: true,
|
||||
});
|
||||
const resData = res.data?.data;
|
||||
let allRows = resData?.data || resData?.rows || [];
|
||||
let rows = resData?.data || resData?.rows || [];
|
||||
const serverTotal = resData?.total || resData?.totalCount || rows.length;
|
||||
|
||||
// 거래처우선일 때 연결된 품목만 표시
|
||||
// 거래처우선일 때 연결된 품목만 표시 (클라이언트 필터)
|
||||
if (customerItemIds) {
|
||||
allRows = allRows.filter((item: any) => customerItemIds!.has(item.item_number) || customerItemIds!.has(item.id));
|
||||
rows = rows.filter((item: any) => customerItemIds!.has(item.item_number) || customerItemIds!.has(item.id));
|
||||
}
|
||||
|
||||
// 관리품목 필터 (코드/라벨 혼재 대응)
|
||||
if (itemSearchDivision !== "all") {
|
||||
const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || "";
|
||||
allRows = allRows.filter((item: any) => {
|
||||
const div = item.division || "";
|
||||
return div.includes(itemSearchDivision) || (divLabel && div.includes(divLabel));
|
||||
});
|
||||
}
|
||||
const total = allRows.length;
|
||||
const start = (p - 1) * s;
|
||||
setItemSearchResults(allRows.slice(start, start + s));
|
||||
setItemTotal(total);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(total / s)));
|
||||
setItemSearchResults(rows);
|
||||
setItemTotal(serverTotal);
|
||||
setItemTotalPages(Math.max(1, Math.ceil(serverTotal / s)));
|
||||
} catch { /* skip */ } finally {
|
||||
setItemSearchLoading(false);
|
||||
}
|
||||
@@ -1318,44 +1320,44 @@ export default function SalesOrderPage() {
|
||||
<Table noWrapper className="min-w-max w-full">
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
<TableHead className="w-10 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="w-28 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="w-24 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="w-20 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="w-20 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="w-24 text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="w-32 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="w-24 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
<TableHead className="min-w-[40px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="min-w-[120px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="min-w-[150px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="min-w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">재질</TableHead>
|
||||
<TableHead className="min-w-[110px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장재</TableHead>
|
||||
<TableHead className="min-w-[100px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="min-w-[90px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">포장수량</TableHead>
|
||||
<TableHead className="min-w-[120px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단가</TableHead>
|
||||
<TableHead className="min-w-[100px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">금액</TableHead>
|
||||
<TableHead className="min-w-[160px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">납기일</TableHead>
|
||||
<TableHead className="min-w-[110px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">분할/삭제</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{detailRows.map((row, idx) => (
|
||||
<TableRow key={row._id || idx}>
|
||||
<TableCell className="text-center font-mono text-[13px] text-muted-foreground/50">{idx + 1}</TableCell>
|
||||
<TableCell className="max-w-[112px]">
|
||||
<span className="block truncate font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="font-mono text-[13px]" title={row.part_code}>{row.part_code}</span>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[128px]">
|
||||
<span className="block truncate text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="text-[13px]" title={row.part_name}>{row.part_name}</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.material}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground whitespace-nowrap">{row.material}</TableCell>
|
||||
<TableCell>
|
||||
<Input
|
||||
value={row.packing_material || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "packing_material", e.target.value)}
|
||||
placeholder="포장재"
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Select value={row.unit || ""} onValueChange={(v) => updateDetailRow(idx, "unit", v)}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 text-xs w-full"><SelectValue placeholder="단위" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["item_unit"] || []).map((o) => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
@@ -1369,7 +1371,7 @@ export default function SalesOrderPage() {
|
||||
min="1"
|
||||
value={row.qty || "1"}
|
||||
onChange={(e) => updateDetailRow(idx, "qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1378,7 +1380,7 @@ export default function SalesOrderPage() {
|
||||
min="0"
|
||||
value={row.pack_qty || "0"}
|
||||
onChange={(e) => updateDetailRow(idx, "pack_qty", e.target.value)}
|
||||
className="h-8 text-xs text-right font-mono w-16"
|
||||
className="h-8 text-xs text-right font-mono w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1386,10 +1388,10 @@ export default function SalesOrderPage() {
|
||||
value={formatNumber(row.unit_price || "")}
|
||||
onChange={(e) => updateDetailRow(idx, "unit_price", parseNumber(e.target.value))}
|
||||
readOnly={!allowPriceEdit}
|
||||
className={cn("h-8 text-xs text-right font-mono w-20", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
className={cn("h-8 text-xs text-right font-mono w-full", !allowPriceEdit && "bg-muted cursor-not-allowed")}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold">
|
||||
<TableCell className="text-right font-mono text-[13px] font-semibold whitespace-nowrap">
|
||||
{row.amount ? Number(row.amount).toLocaleString() : "0"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -1397,7 +1399,7 @@ export default function SalesOrderPage() {
|
||||
type="date"
|
||||
value={row.due_date || ""}
|
||||
onChange={(e) => updateDetailRow(idx, "due_date", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
className="h-8 text-xs w-full"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
@@ -1472,15 +1474,7 @@ export default function SalesOrderPage() {
|
||||
onKeyDown={(e) => e.key === "Enter" && triggerNewSearch()}
|
||||
className="h-9 flex-1"
|
||||
/>
|
||||
<Select value={itemSearchDivision} onValueChange={setItemSearchDivision}>
|
||||
<SelectTrigger className="h-9 w-[130px]"><SelectValue placeholder="관리품목" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">전체</SelectItem>
|
||||
{(categoryOptions["item_division"] || []).map((o) => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="h-9 px-3 flex items-center rounded-md border border-input bg-muted text-xs font-medium text-muted-foreground whitespace-nowrap">영업관리</div>
|
||||
<Button size="sm" onClick={triggerNewSearch} disabled={itemSearchLoading} className="h-9">
|
||||
{itemSearchLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <><Search className="w-4 h-4 mr-1" />조회</>}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user