diff --git a/backend-node/src/controllers/workInstructionController.ts b/backend-node/src/controllers/workInstructionController.ts
index 9d3341d2..9c88f858 100644
--- a/backend-node/src/controllers/workInstructionController.ts
+++ b/backend-node/src/controllers/workInstructionController.ts
@@ -23,7 +23,12 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
try {
await ensureDetailRoutingColumn();
const companyCode = req.user!.companyCode;
- const { dateFrom, dateTo, status, progressStatus, keyword } = req.query;
+ const { dateFrom, dateTo, status, progressStatus, keyword, page, pageSize } = req.query;
+
+ // 페이지네이션 파라미터 파싱 (page 없으면 전체 반환 — 하위호환)
+ const pageNum = page ? Math.max(1, parseInt(page as string, 10) || 1) : null;
+ const sizeNum = pageSize ? Math.max(1, Math.min(1000, parseInt(pageSize as string, 10) || 20)) : null;
+ const paginated = pageNum !== null && sizeNum !== null;
const conditions: string[] = [];
const params: any[] = [];
@@ -54,14 +59,110 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
params.push(progressStatus);
idx++;
}
+ // keyword 검색: wi 자체 필드 + detail.item_number 존재 여부로 EXISTS
if (keyword) {
- conditions.push(`(wi.work_instruction_no ILIKE $${idx} OR wi.worker ILIKE $${idx} OR COALESCE(itm.item_name,'') ILIKE $${idx} OR COALESCE(d.item_number,'') ILIKE $${idx})`);
+ conditions.push(`(
+ wi.work_instruction_no ILIKE $${idx}
+ OR wi.worker ILIKE $${idx}
+ OR EXISTS (
+ SELECT 1 FROM work_instruction_detail dd
+ LEFT JOIN item_info ii ON ii.item_number = dd.item_number AND ii.company_code = wi.company_code
+ WHERE dd.work_instruction_id = wi.id
+ AND (dd.item_number ILIKE $${idx} OR COALESCE(ii.item_name,'') ILIKE $${idx})
+ )
+ )`);
params.push(`%${keyword}%`);
idx++;
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
+ const pool = getPool();
+
+ // 페이지네이션 모드: WI 단위로 페이지 잘라낸 뒤 detail과 JOIN
+ if (paginated) {
+ // 1) 총 WI 개수 카운트
+ const countSql = `
+ SELECT COUNT(*)::int AS cnt
+ FROM work_instruction wi
+ ${whereClause}
+ `;
+ const countRes = await pool.query(countSql, params);
+ const totalCount = countRes.rows[0]?.cnt ?? 0;
+
+ // 2) 현재 페이지 WI id 목록
+ const offset = (pageNum! - 1) * sizeNum!;
+ const pageSql = `
+ SELECT wi.id
+ FROM work_instruction wi
+ ${whereClause}
+ ORDER BY wi.created_date DESC, wi.id DESC
+ LIMIT ${sizeNum} OFFSET ${offset}
+ `;
+ const pageRes = await pool.query(pageSql, params);
+ const wiIds = pageRes.rows.map((r) => r.id);
+
+ if (wiIds.length === 0) {
+ return res.json({ success: true, data: [], totalCount, page: pageNum, pageSize: sizeNum });
+ }
+
+ // 3) 해당 WI들의 detail + 품목/설비/라우팅 JOIN
+ const dataSql = `
+ SELECT
+ wi.id AS wi_id,
+ wi.work_instruction_no,
+ wi.status,
+ wi.progress_status,
+ wi.qty AS total_qty,
+ wi.completed_qty,
+ wi.start_date,
+ wi.end_date,
+ wi.equipment_id,
+ wi.work_team,
+ wi.worker,
+ wi.remark AS wi_remark,
+ wi.created_date,
+ d.id AS detail_id,
+ d.item_number,
+ d.qty AS detail_qty,
+ d.remark AS detail_remark,
+ d.part_code,
+ d.source_table,
+ d.source_id,
+ d.routing_version_id AS detail_routing_version_id,
+ COALESCE(itm.item_name, '') AS item_name,
+ COALESCE(itm.type, '') AS item_type,
+ COALESCE(itm.size, '') AS item_spec,
+ COALESCE(e.equipment_name, '') AS equipment_name,
+ COALESCE(e.equipment_code, '') AS equipment_code,
+ wi.routing AS routing_version_id,
+ COALESCE(rv.version_name, '') AS routing_name,
+ ROW_NUMBER() OVER (PARTITION BY wi.work_instruction_no ORDER BY d.created_date) AS detail_seq,
+ COUNT(*) OVER (PARTITION BY wi.work_instruction_no) AS detail_count
+ FROM work_instruction wi
+ INNER JOIN work_instruction_detail d
+ ON d.work_instruction_id = wi.id
+ LEFT JOIN item_info itm
+ ON itm.item_number = d.item_number AND itm.company_code = wi.company_code
+ LEFT JOIN equipment_mng e
+ ON wi.equipment_id = e.id AND wi.company_code = e.company_code
+ LEFT JOIN item_routing_version rv
+ ON wi.routing = rv.id AND rv.company_code = wi.company_code
+ WHERE wi.id = ANY($1::varchar[])
+ ORDER BY wi.created_date DESC, wi.id DESC, d.created_date ASC
+ `;
+ const dataRes = await pool.query(dataSql, [wiIds]);
+
+ return res.json({
+ success: true,
+ data: dataRes.rows,
+ totalCount,
+ page: pageNum,
+ pageSize: sizeNum,
+ });
+ }
+
+ // 비페이지 모드 (하위호환): 기존 방식 유지, LATERAL만 LEFT JOIN으로 교체
const query = `
SELECT
wi.id AS wi_id,
@@ -97,17 +198,14 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
FROM work_instruction wi
INNER JOIN work_instruction_detail d
ON d.work_instruction_id = wi.id
- LEFT JOIN LATERAL (
- SELECT item_name, size, type FROM item_info
- WHERE item_number = d.item_number AND company_code = wi.company_code LIMIT 1
- ) itm ON true
+ LEFT JOIN item_info itm
+ ON itm.item_number = d.item_number AND itm.company_code = wi.company_code
LEFT JOIN equipment_mng e ON wi.equipment_id = e.id AND wi.company_code = e.company_code
LEFT JOIN item_routing_version rv ON wi.routing = rv.id AND rv.company_code = wi.company_code
${whereClause}
ORDER BY wi.created_date DESC, d.created_date ASC
`;
- const pool = getPool();
const result = await pool.query(query, params);
return res.json({ success: true, data: result.rows });
} catch (error: any) {
diff --git a/frontend/app/(main)/COMPANY_10/purchase/order/page.tsx b/frontend/app/(main)/COMPANY_10/purchase/order/page.tsx
index bf01613e..9ec4e88e 100644
--- a/frontend/app/(main)/COMPANY_10/purchase/order/page.tsx
+++ b/frontend/app/(main)/COMPANY_10/purchase/order/page.tsx
@@ -30,6 +30,7 @@ import { toast } from "sonner";
import { useTableSettings } from "@/hooks/useTableSettings";
import { TableSettingsModal } from "@/components/common/TableSettingsModal";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
+import { SmartSelect } from "@/components/common/SmartSelect";
import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable";
const MASTER_TABLE = "purchase_order_mng";
@@ -1026,7 +1027,8 @@ export default function PurchaseOrderPage() {
-
+ />
diff --git a/frontend/app/(main)/COMPANY_10/sales/order/page.tsx b/frontend/app/(main)/COMPANY_10/sales/order/page.tsx
index f62967f1..e6ec842b 100644
--- a/frontend/app/(main)/COMPANY_10/sales/order/page.tsx
+++ b/frontend/app/(main)/COMPANY_10/sales/order/page.tsx
@@ -28,6 +28,7 @@ import { ShippingPlanBatchModal } from "@/components/common/ShippingPlanBatchMod
import { useTableSettings } from "@/hooks/useTableSettings";
import { TableSettingsModal } from "@/components/common/TableSettingsModal";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
+import { SmartSelect } from "@/components/common/SmartSelect";
import { previewNumberingCode, allocateNumberingCode } from "@/lib/api/numberingRule";
const DETAIL_TABLE = "sales_order_detail";
@@ -1481,17 +1482,12 @@ export default function SalesOrderPage() {
-
+ placeholder="거래처 선택"
+ />
diff --git a/frontend/app/(main)/COMPANY_16/purchase/order/page.tsx b/frontend/app/(main)/COMPANY_16/purchase/order/page.tsx
index e32841d2..dd66b1e5 100644
--- a/frontend/app/(main)/COMPANY_16/purchase/order/page.tsx
+++ b/frontend/app/(main)/COMPANY_16/purchase/order/page.tsx
@@ -30,6 +30,7 @@ import { toast } from "sonner";
import { useTableSettings } from "@/hooks/useTableSettings";
import { TableSettingsModal } from "@/components/common/TableSettingsModal";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
+import { SmartSelect } from "@/components/common/SmartSelect";
import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable";
const MASTER_TABLE = "purchase_order_mng";
@@ -1028,7 +1029,8 @@ export default function PurchaseOrderPage() {
-
+ />
diff --git a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx
index ef5b10aa..766a867c 100644
--- a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx
+++ b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx
@@ -28,6 +28,7 @@ import { ShippingPlanBatchModal } from "@/components/common/ShippingPlanBatchMod
import { useTableSettings } from "@/hooks/useTableSettings";
import { TableSettingsModal } from "@/components/common/TableSettingsModal";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
+import { SmartSelect } from "@/components/common/SmartSelect";
import { previewNumberingCode, allocateNumberingCode } from "@/lib/api/numberingRule";
const DETAIL_TABLE = "sales_order_detail";
@@ -1481,17 +1482,12 @@ export default function SalesOrderPage() {
-
+ placeholder="거래처 선택"
+ />
diff --git a/frontend/app/(main)/COMPANY_29/purchase/order/page.tsx b/frontend/app/(main)/COMPANY_29/purchase/order/page.tsx
index bf01613e..9ec4e88e 100644
--- a/frontend/app/(main)/COMPANY_29/purchase/order/page.tsx
+++ b/frontend/app/(main)/COMPANY_29/purchase/order/page.tsx
@@ -30,6 +30,7 @@ import { toast } from "sonner";
import { useTableSettings } from "@/hooks/useTableSettings";
import { TableSettingsModal } from "@/components/common/TableSettingsModal";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
+import { SmartSelect } from "@/components/common/SmartSelect";
import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable";
const MASTER_TABLE = "purchase_order_mng";
@@ -1026,7 +1027,8 @@ export default function PurchaseOrderPage() {
-
+ />
diff --git a/frontend/app/(main)/COMPANY_29/sales/order/page.tsx b/frontend/app/(main)/COMPANY_29/sales/order/page.tsx
index f62967f1..e6ec842b 100644
--- a/frontend/app/(main)/COMPANY_29/sales/order/page.tsx
+++ b/frontend/app/(main)/COMPANY_29/sales/order/page.tsx
@@ -28,6 +28,7 @@ import { ShippingPlanBatchModal } from "@/components/common/ShippingPlanBatchMod
import { useTableSettings } from "@/hooks/useTableSettings";
import { TableSettingsModal } from "@/components/common/TableSettingsModal";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
+import { SmartSelect } from "@/components/common/SmartSelect";
import { previewNumberingCode, allocateNumberingCode } from "@/lib/api/numberingRule";
const DETAIL_TABLE = "sales_order_detail";
@@ -1481,17 +1482,12 @@ export default function SalesOrderPage() {
-
+ placeholder="거래처 선택"
+ />
diff --git a/frontend/app/(main)/COMPANY_30/production/result/page.tsx b/frontend/app/(main)/COMPANY_30/production/result/page.tsx
index b2176781..e150ab8a 100644
--- a/frontend/app/(main)/COMPANY_30/production/result/page.tsx
+++ b/frontend/app/(main)/COMPANY_30/production/result/page.tsx
@@ -89,6 +89,7 @@ export default function ProductionResultPage() {
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
const [pageSizeInput, setPageSizeInput] = useState("20");
+ const [wiTotalCount, setWiTotalCount] = useState(0);
// ── 우측: 실적 ──
const [rightTab, setRightTab] = useState<"result" | "defect">("result");
@@ -135,7 +136,7 @@ export default function ProductionResultPage() {
const fetchWiList = useCallback(async () => {
setWiLoading(true);
try {
- const params: Record
= {};
+ const params: Record = { page: String(currentPage), pageSize: String(pageSize) };
for (const f of searchFilters) {
if (f.value) {
if (f.columnName === "progress_status") params.progressStatus = f.value;
@@ -145,6 +146,7 @@ export default function ProductionResultPage() {
}
const res = await apiClient.get("/work-instruction/list", { params });
const raw: any[] = res.data?.data || [];
+ const total: number = res.data?.totalCount ?? raw.length;
// work_instruction_no 기준 중복 제거 (detail JOIN으로 여러 행 반환)
const seen = new Set();
@@ -167,15 +169,19 @@ export default function ProductionResultPage() {
};
});
setWiList(enriched);
+ setWiTotalCount(total);
} catch {
toast.error("작업지시 목록 조회 실패");
} finally {
setWiLoading(false);
}
- }, [searchFilters]);
+ }, [searchFilters, currentPage, pageSize]);
useEffect(() => { fetchWiList(); }, [fetchWiList]);
+ // 검색 조건 변경 시 1페이지로 리셋
+ useEffect(() => { setCurrentPage(1); }, [searchFilters]);
+
// 실적 로드
useEffect(() => {
if (!selectedWiId) { setProcessData([]); return; }
@@ -237,13 +243,11 @@ export default function ProductionResultPage() {
return result;
}, [wiList, groupBy]);
- // 페이지네이션 계산
- const totalPages = Math.max(1, Math.ceil(wiList.length / pageSize));
+ // 페이지네이션 계산 (서버사이드)
+ const totalPages = Math.max(1, Math.ceil(wiTotalCount / pageSize));
const safePage = Math.min(Math.max(1, currentPage), totalPages);
- const paginatedRows = useMemo(() => {
- const start = (safePage - 1) * pageSize;
- return wiList.slice(start, start + pageSize);
- }, [wiList, safePage, pageSize]);
+ // 서버가 이미 페이지 분량만 반환하므로 slice 불필요
+ const paginatedRows = wiList;
const paginatedGroupedData = useMemo(() => {
if (groupBy === "none") return paginatedRows;
@@ -283,8 +287,7 @@ export default function ProductionResultPage() {
return pages;
};
- // 필터 변경 시 첫 페이지로 이동
- useEffect(() => { setCurrentPage(1); }, [wiList.length]);
+ // (검색 조건 변경 시 1페이지 리셋은 위 useEffect에서 처리)
const toggleGroup = (key: string) => {
setExpandedGroups((prev) => {
@@ -337,7 +340,7 @@ export default function ProductionResultPage() {
tableName={WI_TABLE}
filterId="c16-production-result"
onFilterChange={setSearchFilters}
- dataCount={wiList.length}
+ dataCount={wiTotalCount}
/>
{/* 메인 */}
@@ -351,7 +354,7 @@ export default function ProductionResultPage() {
작업지시 목록
- {wiList.length}건
+ {wiTotalCount}건
diff --git a/frontend/app/(main)/COMPANY_30/sales/order/page.tsx b/frontend/app/(main)/COMPANY_30/sales/order/page.tsx
index 55a4dcbc..97b09f52 100644
--- a/frontend/app/(main)/COMPANY_30/sales/order/page.tsx
+++ b/frontend/app/(main)/COMPANY_30/sales/order/page.tsx
@@ -34,6 +34,7 @@ import { apiClient } from "@/lib/api/client";
import { useAuth } from "@/hooks/useAuth";
import { toast } from "sonner";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
+import { SmartSelect } from "@/components/common/SmartSelect";
import { DataGrid, DataGridColumn } from "@/components/common/DataGrid";
import { useConfirmDialog } from "@/components/common/ConfirmDialog";
import { FullscreenDialog } from "@/components/common/FullscreenDialog";
@@ -1151,12 +1152,12 @@ export default function ChunganSalesOrderPage() {
- setMasterForm((p) => ({ ...p, partner_id: v }))}>
-
-
- {(categoryOptions["partner_id"] || []).map((o) => {o.label})}
-
-
+ setMasterForm((p) => ({ ...p, partner_id: v }))}
+ placeholder="거래처 선택"
+ />
diff --git a/frontend/app/(main)/COMPANY_7/purchase/order/page.tsx b/frontend/app/(main)/COMPANY_7/purchase/order/page.tsx
index bf01613e..9ec4e88e 100644
--- a/frontend/app/(main)/COMPANY_7/purchase/order/page.tsx
+++ b/frontend/app/(main)/COMPANY_7/purchase/order/page.tsx
@@ -30,6 +30,7 @@ import { toast } from "sonner";
import { useTableSettings } from "@/hooks/useTableSettings";
import { TableSettingsModal } from "@/components/common/TableSettingsModal";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
+import { SmartSelect } from "@/components/common/SmartSelect";
import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable";
const MASTER_TABLE = "purchase_order_mng";
@@ -1026,7 +1027,8 @@ export default function PurchaseOrderPage() {
- {
const supp = categoryOptions["supplier_code"]?.find((o) => o.code === v);
@@ -1034,15 +1036,9 @@ export default function PurchaseOrderPage() {
setMasterForm((p) => ({ ...p, supplier_code: v, supplier_name: name }));
recalcPrices(masterForm.price_mode || "", v);
}}
+ placeholder="공급업체 선택"
disabled={isReadOnly}
- >
-
-
- {(categoryOptions["supplier_code"] || []).map((o) => (
- {o.label}
- ))}
-
-
+ />
diff --git a/frontend/app/(main)/COMPANY_7/sales/order/page.tsx b/frontend/app/(main)/COMPANY_7/sales/order/page.tsx
index f62967f1..e6ec842b 100644
--- a/frontend/app/(main)/COMPANY_7/sales/order/page.tsx
+++ b/frontend/app/(main)/COMPANY_7/sales/order/page.tsx
@@ -28,6 +28,7 @@ import { ShippingPlanBatchModal } from "@/components/common/ShippingPlanBatchMod
import { useTableSettings } from "@/hooks/useTableSettings";
import { TableSettingsModal } from "@/components/common/TableSettingsModal";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
+import { SmartSelect } from "@/components/common/SmartSelect";
import { previewNumberingCode, allocateNumberingCode } from "@/lib/api/numberingRule";
const DETAIL_TABLE = "sales_order_detail";
@@ -1481,17 +1482,12 @@ export default function SalesOrderPage() {
- { setMasterForm((p) => ({ ...p, partner_id: v, delivery_partner_id: "" })); loadDeliveryOptions(v); recalcPrices(masterForm.price_mode || "", v); }}
- >
-
-
- {(categoryOptions["partner_id"] || []).map((o) => (
- {o.label}
- ))}
-
-
+ placeholder="거래처 선택"
+ />
diff --git a/frontend/app/(main)/COMPANY_8/purchase/order/page.tsx b/frontend/app/(main)/COMPANY_8/purchase/order/page.tsx
index bf01613e..9ec4e88e 100644
--- a/frontend/app/(main)/COMPANY_8/purchase/order/page.tsx
+++ b/frontend/app/(main)/COMPANY_8/purchase/order/page.tsx
@@ -30,6 +30,7 @@ import { toast } from "sonner";
import { useTableSettings } from "@/hooks/useTableSettings";
import { TableSettingsModal } from "@/components/common/TableSettingsModal";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
+import { SmartSelect } from "@/components/common/SmartSelect";
import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable";
const MASTER_TABLE = "purchase_order_mng";
@@ -1026,7 +1027,8 @@ export default function PurchaseOrderPage() {
- {
const supp = categoryOptions["supplier_code"]?.find((o) => o.code === v);
@@ -1034,15 +1036,9 @@ export default function PurchaseOrderPage() {
setMasterForm((p) => ({ ...p, supplier_code: v, supplier_name: name }));
recalcPrices(masterForm.price_mode || "", v);
}}
+ placeholder="공급업체 선택"
disabled={isReadOnly}
- >
-
-
- {(categoryOptions["supplier_code"] || []).map((o) => (
- {o.label}
- ))}
-
-
+ />
diff --git a/frontend/app/(main)/COMPANY_8/sales/order/page.tsx b/frontend/app/(main)/COMPANY_8/sales/order/page.tsx
index f62967f1..e6ec842b 100644
--- a/frontend/app/(main)/COMPANY_8/sales/order/page.tsx
+++ b/frontend/app/(main)/COMPANY_8/sales/order/page.tsx
@@ -28,6 +28,7 @@ import { ShippingPlanBatchModal } from "@/components/common/ShippingPlanBatchMod
import { useTableSettings } from "@/hooks/useTableSettings";
import { TableSettingsModal } from "@/components/common/TableSettingsModal";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
+import { SmartSelect } from "@/components/common/SmartSelect";
import { previewNumberingCode, allocateNumberingCode } from "@/lib/api/numberingRule";
const DETAIL_TABLE = "sales_order_detail";
@@ -1481,17 +1482,12 @@ export default function SalesOrderPage() {
- { setMasterForm((p) => ({ ...p, partner_id: v, delivery_partner_id: "" })); loadDeliveryOptions(v); recalcPrices(masterForm.price_mode || "", v); }}
- >
-
-
- {(categoryOptions["partner_id"] || []).map((o) => (
- {o.label}
- ))}
-
-
+ placeholder="거래처 선택"
+ />
diff --git a/frontend/app/(main)/COMPANY_9/purchase/order/page.tsx b/frontend/app/(main)/COMPANY_9/purchase/order/page.tsx
index 840d336b..e47834ef 100644
--- a/frontend/app/(main)/COMPANY_9/purchase/order/page.tsx
+++ b/frontend/app/(main)/COMPANY_9/purchase/order/page.tsx
@@ -30,6 +30,7 @@ import { toast } from "sonner";
import { useTableSettings } from "@/hooks/useTableSettings";
import { TableSettingsModal } from "@/components/common/TableSettingsModal";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
+import { SmartSelect } from "@/components/common/SmartSelect";
import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable";
const MASTER_TABLE = "purchase_order_mng";
@@ -1038,7 +1039,8 @@ export default function PurchaseOrderPage() {
- {
const supp = categoryOptions["supplier_code"]?.find((o) => o.code === v);
@@ -1046,15 +1048,9 @@ export default function PurchaseOrderPage() {
setMasterForm((p) => ({ ...p, supplier_code: v, supplier_name: name }));
recalcPrices(masterForm.price_mode || "", v);
}}
+ placeholder="공급업체 선택"
disabled={isReadOnly}
- >
-
-
- {(categoryOptions["supplier_code"] || []).map((o) => (
- {o.label}
- ))}
-
-
+ />
diff --git a/frontend/app/(main)/COMPANY_9/sales/order/page.tsx b/frontend/app/(main)/COMPANY_9/sales/order/page.tsx
index 5cd46fb5..c7be925b 100644
--- a/frontend/app/(main)/COMPANY_9/sales/order/page.tsx
+++ b/frontend/app/(main)/COMPANY_9/sales/order/page.tsx
@@ -31,6 +31,7 @@ import { apiClient } from "@/lib/api/client";
import { useAuth } from "@/hooks/useAuth";
import { toast } from "sonner";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
+import { SmartSelect } from "@/components/common/SmartSelect";
import { DataGrid, DataGridColumn } from "@/components/common/DataGrid";
import { useConfirmDialog } from "@/components/common/ConfirmDialog";
import { FullscreenDialog } from "@/components/common/FullscreenDialog";
@@ -936,12 +937,12 @@ export default function JeilGlassOrderPage() {
- setMasterForm((p) => ({ ...p, partner_id: v }))}>
-
-
- {(categoryOptions["partner_id"] || []).map((o) => {o.label})}
-
-
+ setMasterForm((p) => ({ ...p, partner_id: v }))}
+ placeholder="거래처 선택"
+ />
diff --git a/frontend/components/common/EDataTable.tsx b/frontend/components/common/EDataTable.tsx
index 00bf3486..9e9fac41 100644
--- a/frontend/components/common/EDataTable.tsx
+++ b/frontend/components/common/EDataTable.tsx
@@ -83,6 +83,15 @@ export interface EDataTableProps = any> {
showPagination?: boolean;
defaultPageSize?: number;
+ // ─── 서버사이드 페이지네이션 모드 ───
+ // serverPagination=true 일 때: 내부 slice/filter/sort 미사용, data는 이미 해당 페이지 분량
+ serverPagination?: boolean;
+ serverCurrentPage?: number;
+ serverPageSize?: number;
+ serverTotalCount?: number;
+ onServerPageChange?: (page: number) => void;
+ onServerPageSizeChange?: (size: number) => void;
+
className?: string;
}
@@ -275,6 +284,12 @@ export function EDataTable = any>({
showRowNumber = false,
showPagination = true,
defaultPageSize = 50,
+ serverPagination = false,
+ serverCurrentPage,
+ serverPageSize,
+ serverTotalCount,
+ onServerPageChange,
+ onServerPageSizeChange,
className,
}: EDataTableProps) {
const [columns, setColumns] = useState(initialColumns);
@@ -287,10 +302,21 @@ export function EDataTable = any>({
// 헤더 필터
const [headerFilters, setHeaderFilters] = useState>>({});
- // 페이지네이션
- const [currentPage, setCurrentPage] = useState(1);
- const [pageSize, setPageSize] = useState(defaultPageSize);
- const [pageSizeInput, setPageSizeInput] = useState(String(defaultPageSize));
+ // 페이지네이션 — 서버사이드 모드면 외부 state 사용
+ const [internalCurrentPage, setInternalCurrentPage] = useState(1);
+ const [internalPageSize, setInternalPageSize] = useState(defaultPageSize);
+ const currentPage = serverPagination ? (serverCurrentPage ?? 1) : internalCurrentPage;
+ const pageSize = serverPagination ? (serverPageSize ?? defaultPageSize) : internalPageSize;
+ const setCurrentPage = (next: number | ((prev: number) => number)) => {
+ const resolved = typeof next === "function" ? (next as (p: number) => number)(currentPage) : next;
+ if (serverPagination) onServerPageChange?.(resolved);
+ else setInternalCurrentPage(resolved);
+ };
+ const setPageSize = (n: number) => {
+ if (serverPagination) onServerPageSizeChange?.(n);
+ else setInternalPageSize(n);
+ };
+ const [pageSizeInput, setPageSizeInput] = useState(String(serverPagination ? (serverPageSize ?? defaultPageSize) : defaultPageSize));
// 그룹 접기/펼치기
const [collapsedGroups, setCollapsedGroups] = useState>(new Set());
@@ -394,8 +420,9 @@ export function EDataTable = any>({
});
};
- // 필터 + 정렬
+ // 필터 + 정렬 (서버사이드 모드면 원본 data 그대로 사용)
const processedData = useMemo(() => {
+ if (serverPagination) return data;
let result = [...data];
// 헤더 필터
@@ -425,24 +452,28 @@ export function EDataTable = any>({
}
return result;
- }, [data, headerFilters, sortState, onSortChange]);
+ }, [data, headerFilters, sortState, onSortChange, serverPagination]);
- // 필터/데이터 건수 변경 시 1페이지 리셋 (참조만 바뀐 경우는 리셋 안 함)
- useEffect(() => { setCurrentPage(1); }, [data.length, headerFilters]);
+ // 필터/데이터 건수 변경 시 1페이지 리셋 (서버사이드에선 외부가 제어)
+ useEffect(() => {
+ if (!serverPagination) setCurrentPage(1);
+ }, [data.length, headerFilters, serverPagination]);
// 페이지네이션
- const totalItems = processedData.length;
+ const totalItems = serverPagination ? (serverTotalCount ?? data.length) : processedData.length;
const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
const safePage = Math.min(currentPage, totalPages);
useEffect(() => {
- if (currentPage > totalPages) setCurrentPage(totalPages);
- }, [currentPage, totalPages]);
+ if (!serverPagination && currentPage > totalPages) setCurrentPage(totalPages);
+ }, [currentPage, totalPages, serverPagination]);
const pageOffset = (safePage - 1) * pageSize;
- const paginatedDataRaw = showPagination
- ? processedData.slice(pageOffset, pageOffset + pageSize)
- : processedData;
+ const paginatedDataRaw = serverPagination
+ ? processedData
+ : showPagination
+ ? processedData.slice(pageOffset, pageOffset + pageSize)
+ : processedData;
// 접힌 그룹의 데이터 행 숨김
const paginatedData = useMemo(() => {
diff --git a/frontend/lib/api/workInstruction.ts b/frontend/lib/api/workInstruction.ts
index 20db0997..59a75443 100644
--- a/frontend/lib/api/workInstruction.ts
+++ b/frontend/lib/api/workInstruction.ts
@@ -4,7 +4,7 @@ export interface PaginatedResponse { success: boolean; data: any[]; totalCount:
export async function getWorkInstructionList(params?: Record) {
const res = await apiClient.get("/work-instruction/list", { params });
- return res.data as { success: boolean; data: any[] };
+ return res.data as { success: boolean; data: any[]; totalCount?: number; page?: number; pageSize?: number };
}
export async function previewWorkInstructionNo() {