diff --git a/backend-node/src/controllers/tableManagementController.ts b/backend-node/src/controllers/tableManagementController.ts index 2028dbb8..ae795dc1 100644 --- a/backend-node/src/controllers/tableManagementController.ts +++ b/backend-node/src/controllers/tableManagementController.ts @@ -907,6 +907,61 @@ export async function getTableData( } } +/** + * 테이블 집계 조회 (SUM/COUNT) + * POST /api/table-management/tables/:tableName/aggregate + */ +export async function getTableAggregate( + req: AuthenticatedRequest, + res: Response +): Promise { + try { + const { tableName } = req.params; + const { columns, autoFilter } = req.body; + const companyCode = req.user?.companyCode; + + if (!tableName || !columns || !Array.isArray(columns)) { + res.status(400).json({ success: false, message: "tableName과 columns 배열이 필요합니다." }); + return; + } + + const validCols = columns.filter((c: any) => + c.column && c.func && /^[a-zA-Z0-9_]+$/.test(c.column) && ["sum", "count", "avg", "min", "max"].includes(c.func) + ); + if (validCols.length === 0) { + res.status(400).json({ success: false, message: "유효한 집계 컬럼이 없습니다." }); + return; + } + + const selectParts = validCols.map((c: any) => { + const col = c.column.replace(/[^a-zA-Z0-9_]/g, ""); + return `${c.func}(COALESCE(CAST(NULLIF(${col}, '') AS numeric), 0)) AS "${c.func}_${col}"`; + }); + + let whereClause = ""; + const params: any[] = []; + let paramIdx = 1; + + if (autoFilter !== false && companyCode && companyCode !== "*") { + whereClause = `WHERE company_code = $${paramIdx}`; + params.push(companyCode); + paramIdx++; + } + + const pool = (await import("../database/db")).getPool(); + const safeTable = tableName.replace(/[^a-zA-Z0-9_]/g, ""); + const result = await pool.query( + `SELECT ${selectParts.join(", ")} FROM ${safeTable} ${whereClause}`, + params + ); + + res.json({ success: true, data: result.rows[0] || {} }); + } catch (error: any) { + logger.error("테이블 집계 조회 실패:", error); + res.status(500).json({ success: false, message: error.message }); + } +} + /** * 테이블 데이터 추가 */ diff --git a/backend-node/src/routes/tableManagementRoutes.ts b/backend-node/src/routes/tableManagementRoutes.ts index 6a4a8ce8..c0ba1349 100644 --- a/backend-node/src/routes/tableManagementRoutes.ts +++ b/backend-node/src/routes/tableManagementRoutes.ts @@ -11,7 +11,8 @@ import { updateColumnInputType, updateTableLabel, getTableData, - getTableRecord, // 🆕 단일 레코드 조회 + getTableRecord, + getTableAggregate, addTableData, editTableData, deleteTableData, @@ -193,6 +194,7 @@ router.get("/health", checkDatabaseConnection); * POST /api/table-management/tables/:tableName/data */ router.post("/tables/:tableName/data", getTableData); +router.post("/tables/:tableName/aggregate", getTableAggregate); /** * 단일 레코드 조회 (자동 입력용) diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index 9d63a366..3ad0e3f7 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -2367,26 +2367,24 @@ export class TableManagementService { const total = parseInt(countResult[0].count); // 데이터 조회 (main 별칭 추가) - const dataQuery = ` - SELECT main.* FROM ${safeTableName} main - ${whereClause} - ${orderClause} - LIMIT $${paramIndex} OFFSET $${paramIndex + 1} - `; + // size=0 이면 LIMIT 없이 전체 반환 (마스터 참조 데이터 조회용) + const usePaging = size > 0; + const dataQuery = usePaging + ? `SELECT main.* FROM ${safeTableName} main ${whereClause} ${orderClause} LIMIT $${paramIndex} OFFSET $${paramIndex + 1}` + : `SELECT main.* FROM ${safeTableName} main ${whereClause} ${orderClause}`; logger.info(`🔍 실행할 SQL: ${dataQuery}`); - logger.info( - `🔍 파라미터: ${JSON.stringify([...searchValues, size, offset])}` - ); + const queryParams = usePaging ? [...searchValues, size, offset] : [...searchValues]; + logger.info(`🔍 파라미터: ${JSON.stringify(queryParams)}`); - let data = await query(dataQuery, [...searchValues, size, offset]); + let data = await query(dataQuery, queryParams); // 🎯 파일 컬럼이 있으면 파일 정보 보강 if (fileColumns.length > 0) { data = await this.enrichFileData(data, fileColumns, safeTableName); } - const totalPages = Math.ceil(total / size); + const totalPages = usePaging ? Math.ceil(total / size) : 1; logger.info( `테이블 데이터 조회 완료: ${tableName}, 총 ${total}건, ${data.length}개 반환` diff --git a/frontend/app/(main)/COMPANY_30/sales/order/page.tsx b/frontend/app/(main)/COMPANY_30/sales/order/page.tsx index 708c9840..f99de70e 100644 --- a/frontend/app/(main)/COMPANY_30/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_30/sales/order/page.tsx @@ -24,6 +24,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen import { Plus, Trash2, Save, Loader2, FileSpreadsheet, Download, Pencil, ClipboardList, Package, Search, X, Settings2, GripVertical, + ChevronsLeft, ChevronLeft, ChevronRight, ChevronsRight, } from "lucide-react"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; @@ -94,6 +95,12 @@ export default function JeilGlassOrderPage() { const [loading, setLoading] = useState(false); const [totalCount, setTotalCount] = useState(0); const [searchFilters, setSearchFilters] = useState([]); + // 전체 통계 (서버에서 별도 집계) + const [totalStats, setTotalStats] = useState<{ totalAmount: number; totalQty: number }>({ totalAmount: 0, totalQty: 0 }); + // 좌측 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); const [selectedOrderNo, setSelectedOrderNo] = useState(null); const [checkedIds, setCheckedIds] = useState([]); @@ -168,7 +175,7 @@ export default function JeilGlassOrderPage() { } // 거래처 try { - const res = await apiClient.post(`/table-management/tables/customer_mng/data`, { page: 1, size: 500, autoFilter: true }); + const res = await apiClient.post(`/table-management/tables/customer_mng/data`, { page: 1, size: 5000, autoFilter: true }); const custs = res.data?.data?.data || res.data?.data?.rows || []; optMap["partner_id"] = custs.map((c: any) => ({ code: c.customer_code, @@ -198,7 +205,7 @@ export default function JeilGlassOrderPage() { loadCategories(); }, []); - // 수주 목록 조회 (디테일 전체 → order_no 그룹핑) + // 수주 목록 조회 (마스터 서버 페이징 → 디테일 조인) const fetchMasterOrders = useCallback(async () => { setLoading(true); try { @@ -207,29 +214,36 @@ export default function JeilGlassOrderPage() { operator: f.operator, value: f.value, })); - const res = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, { - page: 1, size: 500, + + // 1단계: 마스터 서버 페이징 조회 + const mRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, { + page: currentPage, size: pageSize, dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined, autoFilter: true, sort: { columnName: "order_no", order: "desc" }, }); - const rows = res.data?.data?.data || res.data?.data?.rows || []; - setAllDetails(rows); + const masters = mRes.data?.data?.data || mRes.data?.data?.rows || []; + const serverTotal = mRes.data?.data?.total || mRes.data?.data?.totalCount || masters.length; + setTotalCount(serverTotal); - // 마스터 조회 (거래처 정보 확보) - const orderNos = [...new Set(rows.map((r: any) => r.order_no).filter(Boolean))]; - let masterMap: Record = {}; + // 2단계: 해당 페이지 마스터의 order_no로 디테일 조회 + const orderNos = masters.map((m: any) => m.order_no).filter(Boolean); + let allRows: any[] = []; if (orderNos.length > 0) { try { - const mRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, { - page: 1, size: orderNos.length + 10, + const dRes = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, { + page: 1, size: orderNos.length * 50, dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "in", value: orderNos }] }, autoFilter: true, + sort: { columnName: "order_no", order: "desc" }, }); - const masters = mRes.data?.data?.data || mRes.data?.data?.rows || []; - for (const m of masters) masterMap[m.order_no] = m; + allRows = dRes.data?.data?.data || dRes.data?.data?.rows || []; } catch { /* skip */ } } + setAllDetails(allRows); + + const masterMap: Record = {}; + for (const m of masters) masterMap[m.order_no] = m; // 거래처 코드 → 이름 변환 const resolvePartner = (code: string) => { @@ -237,26 +251,28 @@ export default function JeilGlassOrderPage() { return categoryOptions["partner_id"]?.find((o) => o.code === code)?.label?.split(" (")[0] || code; }; - // order_no 기준 집계 + // order_no 기준 집계 (마스터 기반으로 생성, 디테일 누적) const grouped: Record = {}; - for (const row of rows) { - const no = row.order_no; + for (const m of masters) { + const no = m.order_no; if (!no) continue; - if (!grouped[no]) { - const master = masterMap[no] || {}; - grouped[no] = { - id: `master_${no}`, - order_no: no, - partner_name: resolvePartner(master.partner_id), - item_count: 0, - total_qty: 0, - total_ship_qty: 0, - total_balance: 0, - total_amount: 0, - due_date: row.due_date || "", - status: master.status || "", - }; - } + grouped[no] = { + id: `master_${no}`, + order_no: no, + partner_name: resolvePartner(m.partner_id), + item_count: 0, + total_qty: 0, + total_ship_qty: 0, + total_balance: 0, + total_amount: 0, + due_date: m.due_date || "", + status: m.status || "", + }; + } + // 디테일 기준 집계 + for (const row of allRows) { + const no = row.order_no; + if (!no || !grouped[no]) continue; const g = grouped[no]; g.item_count += 1; g.total_qty += parseFloat(row.qty) || 0; @@ -267,26 +283,59 @@ export default function JeilGlassOrderPage() { } const list = Object.values(grouped); setMasterOrders(list); - setTotalCount(list.length); + + // 전체 통계: DB에서 직접 SUM (size:99999 전체 조회 대신) + try { + const aggRes = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/aggregate`, { + columns: [ + { column: "qty", func: "sum" }, + { column: "amount", func: "sum" }, + ], + autoFilter: true, + }); + if (aggRes.data?.success) { + setTotalStats({ + totalQty: Number(aggRes.data.data?.sum_qty) || 0, + totalAmount: Number(aggRes.data.data?.sum_amount) || 0, + }); + } + } catch { /* skip */ } } catch (err) { console.error("수주 조회 실패:", err); toast.error("수주 목록을 불러오는데 실패했습니다."); } finally { setLoading(false); } - }, [searchFilters, categoryOptions]); + }, [searchFilters, categoryOptions, currentPage, pageSize]); useEffect(() => { fetchMasterOrders(); }, [fetchMasterOrders]); - // 통계 - const stats = useMemo(() => { - let totalAmount = 0, totalQty = 0; - for (const m of masterOrders) { - totalAmount += m.total_amount || 0; - totalQty += m.total_qty || 0; + // 서버 페이징 계산 + const totalPages = Math.max(1, Math.ceil(totalCount / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + + const applyPageSize = () => { + const n = parseInt(pageSizeInput, 10); + if (!isNaN(n) && n >= 1) { setPageSize(n); setCurrentPage(1); } + else setPageSizeInput(String(pageSize)); + }; + + const getPageNumbers = (): (number | "...")[] => { + const pages: (number | "...")[] = []; + if (totalPages <= 7) { + for (let i = 1; i <= totalPages; i++) pages.push(i); + } else { + pages.push(1); + if (safePage > 3) pages.push("..."); + for (let i = Math.max(2, safePage - 1); i <= Math.min(totalPages - 1, safePage + 1); i++) pages.push(i); + if (safePage < totalPages - 2) pages.push("..."); + pages.push(totalPages); } - return { totalAmount, totalQty }; - }, [masterOrders]); + return pages; + }; + + // 통계 (전체 기준) + const stats = totalStats; // 우측: 선택된 수주 디테일 조회 (division 코드→라벨 변환) useEffect(() => { @@ -766,6 +815,7 @@ export default function JeilGlassOrderPage() { data={masterOrders} loading={loading} showCheckbox + showPagination={false} checkedIds={checkedIds} onCheckedChange={setCheckedIds} onRowClick={handleMasterRowClick} @@ -773,6 +823,38 @@ export default function JeilGlassOrderPage() { tableName={MASTER_TABLE} emptyMessage="등록된 수주가 없습니다" /> + {/* 페이지네이션 */} +
+
+ 전체 {totalCount} +
+ setPageSizeInput(e.target.value)} + onBlur={applyPageSize} + onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); applyPageSize(); } }} + className="h-7 w-16 text-center text-xs" /> + 건씩 +
+
+
+ + + {getPageNumbers().map((page, idx) => + page === "..." ? ( + ... + ) : ( + + ) + )} + + +
+ {safePage} / {totalPages} +
diff --git a/frontend/app/(main)/COMPANY_9/sales/order/page.tsx b/frontend/app/(main)/COMPANY_9/sales/order/page.tsx index 6f4502bd..f99de70e 100644 --- a/frontend/app/(main)/COMPANY_9/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_9/sales/order/page.tsx @@ -24,6 +24,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen import { Plus, Trash2, Save, Loader2, FileSpreadsheet, Download, Pencil, ClipboardList, Package, Search, X, Settings2, GripVertical, + ChevronsLeft, ChevronLeft, ChevronRight, ChevronsRight, } from "lucide-react"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; @@ -94,6 +95,12 @@ export default function JeilGlassOrderPage() { const [loading, setLoading] = useState(false); const [totalCount, setTotalCount] = useState(0); const [searchFilters, setSearchFilters] = useState([]); + // 전체 통계 (서버에서 별도 집계) + const [totalStats, setTotalStats] = useState<{ totalAmount: number; totalQty: number }>({ totalAmount: 0, totalQty: 0 }); + // 좌측 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); const [selectedOrderNo, setSelectedOrderNo] = useState(null); const [checkedIds, setCheckedIds] = useState([]); @@ -168,7 +175,7 @@ export default function JeilGlassOrderPage() { } // 거래처 try { - const res = await apiClient.post(`/table-management/tables/customer_mng/data`, { page: 1, size: 500, autoFilter: true }); + const res = await apiClient.post(`/table-management/tables/customer_mng/data`, { page: 1, size: 5000, autoFilter: true }); const custs = res.data?.data?.data || res.data?.data?.rows || []; optMap["partner_id"] = custs.map((c: any) => ({ code: c.customer_code, @@ -198,7 +205,7 @@ export default function JeilGlassOrderPage() { loadCategories(); }, []); - // 수주 목록 조회 (디테일 전체 → order_no 그룹핑) + // 수주 목록 조회 (마스터 서버 페이징 → 디테일 조인) const fetchMasterOrders = useCallback(async () => { setLoading(true); try { @@ -207,29 +214,36 @@ export default function JeilGlassOrderPage() { operator: f.operator, value: f.value, })); - const res = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, { - page: 1, size: 500, + + // 1단계: 마스터 서버 페이징 조회 + const mRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, { + page: currentPage, size: pageSize, dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined, autoFilter: true, sort: { columnName: "order_no", order: "desc" }, }); - const rows = res.data?.data?.data || res.data?.data?.rows || []; - setAllDetails(rows); + const masters = mRes.data?.data?.data || mRes.data?.data?.rows || []; + const serverTotal = mRes.data?.data?.total || mRes.data?.data?.totalCount || masters.length; + setTotalCount(serverTotal); - // 마스터 조회 (거래처 정보 확보) - const orderNos = [...new Set(rows.map((r: any) => r.order_no).filter(Boolean))]; - let masterMap: Record = {}; + // 2단계: 해당 페이지 마스터의 order_no로 디테일 조회 + const orderNos = masters.map((m: any) => m.order_no).filter(Boolean); + let allRows: any[] = []; if (orderNos.length > 0) { try { - const mRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, { - page: 1, size: orderNos.length + 10, + const dRes = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, { + page: 1, size: orderNos.length * 50, dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "in", value: orderNos }] }, autoFilter: true, + sort: { columnName: "order_no", order: "desc" }, }); - const masters = mRes.data?.data?.data || mRes.data?.data?.rows || []; - for (const m of masters) masterMap[m.order_no] = m; + allRows = dRes.data?.data?.data || dRes.data?.data?.rows || []; } catch { /* skip */ } } + setAllDetails(allRows); + + const masterMap: Record = {}; + for (const m of masters) masterMap[m.order_no] = m; // 거래처 코드 → 이름 변환 const resolvePartner = (code: string) => { @@ -237,26 +251,28 @@ export default function JeilGlassOrderPage() { return categoryOptions["partner_id"]?.find((o) => o.code === code)?.label?.split(" (")[0] || code; }; - // order_no 기준 집계 + // order_no 기준 집계 (마스터 기반으로 생성, 디테일 누적) const grouped: Record = {}; - for (const row of rows) { - const no = row.order_no; + for (const m of masters) { + const no = m.order_no; if (!no) continue; - if (!grouped[no]) { - const master = masterMap[no] || {}; - grouped[no] = { - id: `master_${no}`, - order_no: no, - partner_name: resolvePartner(master.partner_id), - item_count: 0, - total_qty: 0, - total_ship_qty: 0, - total_balance: 0, - total_amount: 0, - due_date: row.due_date || "", - status: master.status || "", - }; - } + grouped[no] = { + id: `master_${no}`, + order_no: no, + partner_name: resolvePartner(m.partner_id), + item_count: 0, + total_qty: 0, + total_ship_qty: 0, + total_balance: 0, + total_amount: 0, + due_date: m.due_date || "", + status: m.status || "", + }; + } + // 디테일 기준 집계 + for (const row of allRows) { + const no = row.order_no; + if (!no || !grouped[no]) continue; const g = grouped[no]; g.item_count += 1; g.total_qty += parseFloat(row.qty) || 0; @@ -267,26 +283,59 @@ export default function JeilGlassOrderPage() { } const list = Object.values(grouped); setMasterOrders(list); - setTotalCount(list.length); + + // 전체 통계: DB에서 직접 SUM (size:99999 전체 조회 대신) + try { + const aggRes = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/aggregate`, { + columns: [ + { column: "qty", func: "sum" }, + { column: "amount", func: "sum" }, + ], + autoFilter: true, + }); + if (aggRes.data?.success) { + setTotalStats({ + totalQty: Number(aggRes.data.data?.sum_qty) || 0, + totalAmount: Number(aggRes.data.data?.sum_amount) || 0, + }); + } + } catch { /* skip */ } } catch (err) { console.error("수주 조회 실패:", err); toast.error("수주 목록을 불러오는데 실패했습니다."); } finally { setLoading(false); } - }, [searchFilters, categoryOptions]); + }, [searchFilters, categoryOptions, currentPage, pageSize]); useEffect(() => { fetchMasterOrders(); }, [fetchMasterOrders]); - // 통계 - const stats = useMemo(() => { - let totalAmount = 0, totalQty = 0; - for (const m of masterOrders) { - totalAmount += m.total_amount || 0; - totalQty += m.total_qty || 0; + // 서버 페이징 계산 + const totalPages = Math.max(1, Math.ceil(totalCount / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + + const applyPageSize = () => { + const n = parseInt(pageSizeInput, 10); + if (!isNaN(n) && n >= 1) { setPageSize(n); setCurrentPage(1); } + else setPageSizeInput(String(pageSize)); + }; + + const getPageNumbers = (): (number | "...")[] => { + const pages: (number | "...")[] = []; + if (totalPages <= 7) { + for (let i = 1; i <= totalPages; i++) pages.push(i); + } else { + pages.push(1); + if (safePage > 3) pages.push("..."); + for (let i = Math.max(2, safePage - 1); i <= Math.min(totalPages - 1, safePage + 1); i++) pages.push(i); + if (safePage < totalPages - 2) pages.push("..."); + pages.push(totalPages); } - return { totalAmount, totalQty }; - }, [masterOrders]); + return pages; + }; + + // 통계 (전체 기준) + const stats = totalStats; // 우측: 선택된 수주 디테일 조회 (division 코드→라벨 변환) useEffect(() => { @@ -537,19 +586,27 @@ export default function JeilGlassOrderPage() { if (!code) return ""; return categoryOptions["item_division"]?.find((o) => o.code === code)?.label || code; }; - const newRows = selected.map((item) => ({ - _id: `new_${Date.now()}_${Math.random()}`, - _fromItemInfo: true, - part_code: item.item_number || "", - part_name: item.item_name || "", - spec: item.size || "", - division: item.division || "", - _divisionLabel: resolveDivision(item.division), - unit: resolveUnit(item.unit) || "", - width: "", height: "", thickness: "", area: "", - qty: "", unit_price: item.selling_price || item.standard_price || "", amount: "", - due_date: "", memo: "", - })); + const newRows = selected.map((item) => { + const w = parseFloat(item.width) || 0; + const h = parseFloat(item.height) || 0; + const autoArea = w > 0 && h > 0 ? String(Math.round((w * h / 1_000_000) * 10000) / 10000) : ""; + return { + _id: `new_${Date.now()}_${Math.random()}`, + _fromItemInfo: true, + part_code: item.item_number || "", + part_name: item.item_name || "", + spec: item.size || "", + division: item.division || "", + _divisionLabel: resolveDivision(item.division), + unit: resolveUnit(item.unit) || "", + width: item.width || "", + height: item.height || "", + thickness: item.thickness || "", + area: item.area || autoArea, + qty: "", unit_price: item.selling_price || item.standard_price || "", amount: "", + due_date: "", memo: "", + }; + }); setModalDetailRows((prev) => [...prev, ...newRows]); setItemSelectOpen(false); setItemCheckedIds(new Set()); @@ -758,6 +815,7 @@ export default function JeilGlassOrderPage() { data={masterOrders} loading={loading} showCheckbox + showPagination={false} checkedIds={checkedIds} onCheckedChange={setCheckedIds} onRowClick={handleMasterRowClick} @@ -765,6 +823,38 @@ export default function JeilGlassOrderPage() { tableName={MASTER_TABLE} emptyMessage="등록된 수주가 없습니다" /> + {/* 페이지네이션 */} +
+
+ 전체 {totalCount} +
+ setPageSizeInput(e.target.value)} + onBlur={applyPageSize} + onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); applyPageSize(); } }} + className="h-7 w-16 text-center text-xs" /> + 건씩 +
+
+
+ + + {getPageNumbers().map((page, idx) => + page === "..." ? ( + ... + ) : ( + + ) + )} + + +
+ {safePage} / {totalPages} +
@@ -1080,13 +1170,16 @@ export default function JeilGlassOrderPage() { 품목코드 품명 + 가로 + 세로 + 두께 규격 단위 {itemSearchResults.length === 0 ? ( - 검색 결과가 없습니다 + 검색 결과가 없습니다 ) : itemSearchResults.map((item) => ( setItemCheckedIds((prev) => { @@ -1097,6 +1190,9 @@ export default function JeilGlassOrderPage() { {item.item_number} {item.item_name} + {item.width || "-"} + {item.height || "-"} + {item.thickness || "-"} {item.size} {item.unit}