diff --git a/frontend/app/(main)/COMPANY_10/logistics/outbound/page.tsx b/frontend/app/(main)/COMPANY_10/logistics/outbound/page.tsx index 457e2723..27a62198 100644 --- a/frontend/app/(main)/COMPANY_10/logistics/outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_10/logistics/outbound/page.tsx @@ -229,6 +229,11 @@ export default function OutboundPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -397,6 +402,37 @@ export default function OutboundPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -848,7 +884,7 @@ export default function OutboundPage() { -
+
@@ -930,7 +966,7 @@ export default function OutboundPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 출고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_10/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_10/logistics/receiving/page.tsx index 77d21e7b..103affd8 100644 --- a/frontend/app/(main)/COMPANY_10/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_10/logistics/receiving/page.tsx @@ -259,6 +259,11 @@ export default function ReceivingPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -430,6 +435,37 @@ export default function ReceivingPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -878,8 +914,8 @@ export default function ReceivingPage() { /> {/* 입고 목록 테이블 (플랫 리스트) */} -
-
+
+

입고 목록

@@ -891,7 +927,7 @@ export default function ReceivingPage() {
-
+
@@ -973,7 +1009,7 @@ export default function ReceivingPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 입고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_10/production/result/page.tsx b/frontend/app/(main)/COMPANY_10/production/result/page.tsx index 294e722b..b2176781 100644 --- a/frontend/app/(main)/COMPANY_10/production/result/page.tsx +++ b/frontend/app/(main)/COMPANY_10/production/result/page.tsx @@ -22,6 +22,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen import { Download, Loader2, Inbox, Search, BarChart3, AlertTriangle, CheckCircle2, Package, ChevronDown, ChevronRight, ClipboardList, + ChevronLeft, ChevronsLeft, ChevronsRight, } from "lucide-react"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; @@ -84,6 +85,11 @@ export default function ProductionResultPage() { const [groupBy, setGroupBy] = useState("none"); const [expandedGroups, setExpandedGroups] = useState>(new Set()); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // ── 우측: 실적 ── const [rightTab, setRightTab] = useState<"result" | "defect">("result"); const [processData, setProcessData] = useState([]); @@ -231,6 +237,55 @@ export default function ProductionResultPage() { return result; }, [wiList, groupBy]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(wiList.length / 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]); + + const paginatedGroupedData = useMemo(() => { + if (groupBy === "none") return paginatedRows; + const groups = new Map(); + for (const wi of paginatedRows) { + let key = wi[groupBy] || "미지정"; + if (groupBy === "progress_status") key = PROGRESS_LABEL[key] || key; + else if (groupBy === "work_team" || groupBy === "status") key = resolveCategory(groupBy, key) || key; + if (!groups.has(key)) groups.set(key, []); + groups.get(key)!.push(wi); + } + const result: any[] = []; + groups.forEach((items, gk) => { + result.push({ _group: true, _key: gk, _count: items.length }); + result.push(...items); + }); + return result; + }, [paginatedRows, groupBy]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [wiList.length]); + const toggleGroup = (key: string) => { setExpandedGroups((prev) => { const next = new Set(prev); @@ -337,7 +392,7 @@ export default function ProductionResultPage() { - {groupedData.map((row, idx) => { + {paginatedGroupedData.map((row, idx) => { if (row._group) { const expanded = expandedGroups.has(row._key); return ( @@ -398,6 +453,85 @@ export default function ProductionResultPage() { )}
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {wiList.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
diff --git a/frontend/app/(main)/COMPANY_10/sales/order/page.tsx b/frontend/app/(main)/COMPANY_10/sales/order/page.tsx index 76d6a061..4d006b77 100644 --- a/frontend/app/(main)/COMPANY_10/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_10/sales/order/page.tsx @@ -159,6 +159,11 @@ export default function SalesOrderPage() { const [loading, setLoading] = useState(false); const [totalCount, setTotalCount] = useState(0); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 (DynamicSearchFilter에서 관리) const [searchFilters, setSearchFilters] = useState([]); @@ -427,6 +432,37 @@ export default function SalesOrderPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredFlatRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredFlatRows.slice(start, start + pageSize); + }, [filteredFlatRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredFlatRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -931,8 +967,8 @@ export default function SalesOrderPage() {
{/* 데이터 테이블 (플랫 리스트) */} -
-
+
+
@@ -1013,7 +1049,7 @@ export default function SalesOrderPage() { ) : ( - ts.groupData(filteredFlatRows).map((row: any) => { + ts.groupData(paginatedRows).map((row: any) => { // 그룹 요약 행 렌더링 if (row._isGroupSummary) { return ( @@ -1073,6 +1109,85 @@ export default function SalesOrderPage() {
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredFlatRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 수주 등록/수정 모달 */} diff --git a/frontend/app/(main)/COMPANY_16/logistics/outbound/page.tsx b/frontend/app/(main)/COMPANY_16/logistics/outbound/page.tsx index 457e2723..27a62198 100644 --- a/frontend/app/(main)/COMPANY_16/logistics/outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_16/logistics/outbound/page.tsx @@ -229,6 +229,11 @@ export default function OutboundPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -397,6 +402,37 @@ export default function OutboundPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -848,7 +884,7 @@ export default function OutboundPage() {
-
+
@@ -930,7 +966,7 @@ export default function OutboundPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 출고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_16/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_16/logistics/receiving/page.tsx index 77d21e7b..103affd8 100644 --- a/frontend/app/(main)/COMPANY_16/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_16/logistics/receiving/page.tsx @@ -259,6 +259,11 @@ export default function ReceivingPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -430,6 +435,37 @@ export default function ReceivingPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -878,8 +914,8 @@ export default function ReceivingPage() { /> {/* 입고 목록 테이블 (플랫 리스트) */} -
-
+
+

입고 목록

@@ -891,7 +927,7 @@ export default function ReceivingPage() {
-
+
@@ -973,7 +1009,7 @@ export default function ReceivingPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 입고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_16/production/result/page.tsx b/frontend/app/(main)/COMPANY_16/production/result/page.tsx index 294e722b..b2176781 100644 --- a/frontend/app/(main)/COMPANY_16/production/result/page.tsx +++ b/frontend/app/(main)/COMPANY_16/production/result/page.tsx @@ -22,6 +22,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen import { Download, Loader2, Inbox, Search, BarChart3, AlertTriangle, CheckCircle2, Package, ChevronDown, ChevronRight, ClipboardList, + ChevronLeft, ChevronsLeft, ChevronsRight, } from "lucide-react"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; @@ -84,6 +85,11 @@ export default function ProductionResultPage() { const [groupBy, setGroupBy] = useState("none"); const [expandedGroups, setExpandedGroups] = useState>(new Set()); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // ── 우측: 실적 ── const [rightTab, setRightTab] = useState<"result" | "defect">("result"); const [processData, setProcessData] = useState([]); @@ -231,6 +237,55 @@ export default function ProductionResultPage() { return result; }, [wiList, groupBy]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(wiList.length / 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]); + + const paginatedGroupedData = useMemo(() => { + if (groupBy === "none") return paginatedRows; + const groups = new Map(); + for (const wi of paginatedRows) { + let key = wi[groupBy] || "미지정"; + if (groupBy === "progress_status") key = PROGRESS_LABEL[key] || key; + else if (groupBy === "work_team" || groupBy === "status") key = resolveCategory(groupBy, key) || key; + if (!groups.has(key)) groups.set(key, []); + groups.get(key)!.push(wi); + } + const result: any[] = []; + groups.forEach((items, gk) => { + result.push({ _group: true, _key: gk, _count: items.length }); + result.push(...items); + }); + return result; + }, [paginatedRows, groupBy]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [wiList.length]); + const toggleGroup = (key: string) => { setExpandedGroups((prev) => { const next = new Set(prev); @@ -337,7 +392,7 @@ export default function ProductionResultPage() { - {groupedData.map((row, idx) => { + {paginatedGroupedData.map((row, idx) => { if (row._group) { const expanded = expandedGroups.has(row._key); return ( @@ -398,6 +453,85 @@ export default function ProductionResultPage() { )}
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {wiList.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
diff --git a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx index 76d6a061..4d006b77 100644 --- a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx @@ -159,6 +159,11 @@ export default function SalesOrderPage() { const [loading, setLoading] = useState(false); const [totalCount, setTotalCount] = useState(0); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 (DynamicSearchFilter에서 관리) const [searchFilters, setSearchFilters] = useState([]); @@ -427,6 +432,37 @@ export default function SalesOrderPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredFlatRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredFlatRows.slice(start, start + pageSize); + }, [filteredFlatRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredFlatRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -931,8 +967,8 @@ export default function SalesOrderPage() {
{/* 데이터 테이블 (플랫 리스트) */} -
-
+
+
@@ -1013,7 +1049,7 @@ export default function SalesOrderPage() { ) : ( - ts.groupData(filteredFlatRows).map((row: any) => { + ts.groupData(paginatedRows).map((row: any) => { // 그룹 요약 행 렌더링 if (row._isGroupSummary) { return ( @@ -1073,6 +1109,85 @@ export default function SalesOrderPage() {
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredFlatRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 수주 등록/수정 모달 */} diff --git a/frontend/app/(main)/COMPANY_29/logistics/outbound/page.tsx b/frontend/app/(main)/COMPANY_29/logistics/outbound/page.tsx index 457e2723..27a62198 100644 --- a/frontend/app/(main)/COMPANY_29/logistics/outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_29/logistics/outbound/page.tsx @@ -229,6 +229,11 @@ export default function OutboundPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -397,6 +402,37 @@ export default function OutboundPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -848,7 +884,7 @@ export default function OutboundPage() {
-
+
@@ -930,7 +966,7 @@ export default function OutboundPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 출고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_29/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_29/logistics/receiving/page.tsx index 77d21e7b..103affd8 100644 --- a/frontend/app/(main)/COMPANY_29/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_29/logistics/receiving/page.tsx @@ -259,6 +259,11 @@ export default function ReceivingPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -430,6 +435,37 @@ export default function ReceivingPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -878,8 +914,8 @@ export default function ReceivingPage() { /> {/* 입고 목록 테이블 (플랫 리스트) */} -
-
+
+

입고 목록

@@ -891,7 +927,7 @@ export default function ReceivingPage() {
-
+
@@ -973,7 +1009,7 @@ export default function ReceivingPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 입고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_29/production/result/page.tsx b/frontend/app/(main)/COMPANY_29/production/result/page.tsx index 294e722b..b2176781 100644 --- a/frontend/app/(main)/COMPANY_29/production/result/page.tsx +++ b/frontend/app/(main)/COMPANY_29/production/result/page.tsx @@ -22,6 +22,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen import { Download, Loader2, Inbox, Search, BarChart3, AlertTriangle, CheckCircle2, Package, ChevronDown, ChevronRight, ClipboardList, + ChevronLeft, ChevronsLeft, ChevronsRight, } from "lucide-react"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; @@ -84,6 +85,11 @@ export default function ProductionResultPage() { const [groupBy, setGroupBy] = useState("none"); const [expandedGroups, setExpandedGroups] = useState>(new Set()); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // ── 우측: 실적 ── const [rightTab, setRightTab] = useState<"result" | "defect">("result"); const [processData, setProcessData] = useState([]); @@ -231,6 +237,55 @@ export default function ProductionResultPage() { return result; }, [wiList, groupBy]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(wiList.length / 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]); + + const paginatedGroupedData = useMemo(() => { + if (groupBy === "none") return paginatedRows; + const groups = new Map(); + for (const wi of paginatedRows) { + let key = wi[groupBy] || "미지정"; + if (groupBy === "progress_status") key = PROGRESS_LABEL[key] || key; + else if (groupBy === "work_team" || groupBy === "status") key = resolveCategory(groupBy, key) || key; + if (!groups.has(key)) groups.set(key, []); + groups.get(key)!.push(wi); + } + const result: any[] = []; + groups.forEach((items, gk) => { + result.push({ _group: true, _key: gk, _count: items.length }); + result.push(...items); + }); + return result; + }, [paginatedRows, groupBy]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [wiList.length]); + const toggleGroup = (key: string) => { setExpandedGroups((prev) => { const next = new Set(prev); @@ -337,7 +392,7 @@ export default function ProductionResultPage() { - {groupedData.map((row, idx) => { + {paginatedGroupedData.map((row, idx) => { if (row._group) { const expanded = expandedGroups.has(row._key); return ( @@ -398,6 +453,85 @@ export default function ProductionResultPage() { )}
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {wiList.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
diff --git a/frontend/app/(main)/COMPANY_29/sales/order/page.tsx b/frontend/app/(main)/COMPANY_29/sales/order/page.tsx index 76d6a061..4d006b77 100644 --- a/frontend/app/(main)/COMPANY_29/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_29/sales/order/page.tsx @@ -159,6 +159,11 @@ export default function SalesOrderPage() { const [loading, setLoading] = useState(false); const [totalCount, setTotalCount] = useState(0); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 (DynamicSearchFilter에서 관리) const [searchFilters, setSearchFilters] = useState([]); @@ -427,6 +432,37 @@ export default function SalesOrderPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredFlatRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredFlatRows.slice(start, start + pageSize); + }, [filteredFlatRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredFlatRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -931,8 +967,8 @@ export default function SalesOrderPage() {
{/* 데이터 테이블 (플랫 리스트) */} -
-
+
+
@@ -1013,7 +1049,7 @@ export default function SalesOrderPage() { ) : ( - ts.groupData(filteredFlatRows).map((row: any) => { + ts.groupData(paginatedRows).map((row: any) => { // 그룹 요약 행 렌더링 if (row._isGroupSummary) { return ( @@ -1073,6 +1109,85 @@ export default function SalesOrderPage() {
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredFlatRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 수주 등록/수정 모달 */} diff --git a/frontend/app/(main)/COMPANY_30/logistics/outbound/page.tsx b/frontend/app/(main)/COMPANY_30/logistics/outbound/page.tsx index 457e2723..27a62198 100644 --- a/frontend/app/(main)/COMPANY_30/logistics/outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_30/logistics/outbound/page.tsx @@ -229,6 +229,11 @@ export default function OutboundPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -397,6 +402,37 @@ export default function OutboundPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -848,7 +884,7 @@ export default function OutboundPage() {
-
+
@@ -930,7 +966,7 @@ export default function OutboundPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 출고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_30/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_30/logistics/receiving/page.tsx index 77d21e7b..103affd8 100644 --- a/frontend/app/(main)/COMPANY_30/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_30/logistics/receiving/page.tsx @@ -259,6 +259,11 @@ export default function ReceivingPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -430,6 +435,37 @@ export default function ReceivingPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -878,8 +914,8 @@ export default function ReceivingPage() { /> {/* 입고 목록 테이블 (플랫 리스트) */} -
-
+
+

입고 목록

@@ -891,7 +927,7 @@ export default function ReceivingPage() {
-
+
@@ -973,7 +1009,7 @@ export default function ReceivingPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 입고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_30/production/result/page.tsx b/frontend/app/(main)/COMPANY_30/production/result/page.tsx index 294e722b..b2176781 100644 --- a/frontend/app/(main)/COMPANY_30/production/result/page.tsx +++ b/frontend/app/(main)/COMPANY_30/production/result/page.tsx @@ -22,6 +22,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen import { Download, Loader2, Inbox, Search, BarChart3, AlertTriangle, CheckCircle2, Package, ChevronDown, ChevronRight, ClipboardList, + ChevronLeft, ChevronsLeft, ChevronsRight, } from "lucide-react"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; @@ -84,6 +85,11 @@ export default function ProductionResultPage() { const [groupBy, setGroupBy] = useState("none"); const [expandedGroups, setExpandedGroups] = useState>(new Set()); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // ── 우측: 실적 ── const [rightTab, setRightTab] = useState<"result" | "defect">("result"); const [processData, setProcessData] = useState([]); @@ -231,6 +237,55 @@ export default function ProductionResultPage() { return result; }, [wiList, groupBy]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(wiList.length / 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]); + + const paginatedGroupedData = useMemo(() => { + if (groupBy === "none") return paginatedRows; + const groups = new Map(); + for (const wi of paginatedRows) { + let key = wi[groupBy] || "미지정"; + if (groupBy === "progress_status") key = PROGRESS_LABEL[key] || key; + else if (groupBy === "work_team" || groupBy === "status") key = resolveCategory(groupBy, key) || key; + if (!groups.has(key)) groups.set(key, []); + groups.get(key)!.push(wi); + } + const result: any[] = []; + groups.forEach((items, gk) => { + result.push({ _group: true, _key: gk, _count: items.length }); + result.push(...items); + }); + return result; + }, [paginatedRows, groupBy]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [wiList.length]); + const toggleGroup = (key: string) => { setExpandedGroups((prev) => { const next = new Set(prev); @@ -337,7 +392,7 @@ export default function ProductionResultPage() { - {groupedData.map((row, idx) => { + {paginatedGroupedData.map((row, idx) => { if (row._group) { const expanded = expandedGroups.has(row._key); return ( @@ -398,6 +453,85 @@ export default function ProductionResultPage() { )}
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {wiList.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
diff --git a/frontend/app/(main)/COMPANY_30/sales/order/page.tsx b/frontend/app/(main)/COMPANY_30/sales/order/page.tsx index 8f137c7a..4d006b77 100644 --- a/frontend/app/(main)/COMPANY_30/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_30/sales/order/page.tsx @@ -159,6 +159,11 @@ export default function SalesOrderPage() { const [loading, setLoading] = useState(false); const [totalCount, setTotalCount] = useState(0); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 (DynamicSearchFilter에서 관리) const [searchFilters, setSearchFilters] = useState([]); @@ -427,6 +432,37 @@ export default function SalesOrderPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredFlatRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredFlatRows.slice(start, start + pageSize); + }, [filteredFlatRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredFlatRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -931,8 +967,8 @@ export default function SalesOrderPage() {
{/* 데이터 테이블 (플랫 리스트) */} -
-
+
+
@@ -1013,7 +1049,19 @@ export default function SalesOrderPage() { ) : ( - filteredFlatRows.map((row) => { + ts.groupData(paginatedRows).map((row: any) => { + // 그룹 요약 행 렌더링 + if (row._isGroupSummary) { + return ( + + + {row._groupLabel || "합계"}: {row._count ? `${row._count}건` : ""} + {row.qty ? ` · 수량 ${Number(row.qty).toLocaleString()}` : ""} + {row.amount ? ` · 금액 ${Number(row.amount).toLocaleString()}` : ""} + + + ); + } const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredFlatRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 수주 등록/수정 모달 */} diff --git a/frontend/app/(main)/COMPANY_7/logistics/outbound/page.tsx b/frontend/app/(main)/COMPANY_7/logistics/outbound/page.tsx index 457e2723..27a62198 100644 --- a/frontend/app/(main)/COMPANY_7/logistics/outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_7/logistics/outbound/page.tsx @@ -229,6 +229,11 @@ export default function OutboundPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -397,6 +402,37 @@ export default function OutboundPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -848,7 +884,7 @@ export default function OutboundPage() {
-
+
@@ -930,7 +966,7 @@ export default function OutboundPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 출고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_7/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_7/logistics/receiving/page.tsx index 77d21e7b..103affd8 100644 --- a/frontend/app/(main)/COMPANY_7/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_7/logistics/receiving/page.tsx @@ -259,6 +259,11 @@ export default function ReceivingPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -430,6 +435,37 @@ export default function ReceivingPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -878,8 +914,8 @@ export default function ReceivingPage() { /> {/* 입고 목록 테이블 (플랫 리스트) */} -
-
+
+

입고 목록

@@ -891,7 +927,7 @@ export default function ReceivingPage() {
-
+
@@ -973,7 +1009,7 @@ export default function ReceivingPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 입고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_7/production/result/page.tsx b/frontend/app/(main)/COMPANY_7/production/result/page.tsx index 294e722b..b2176781 100644 --- a/frontend/app/(main)/COMPANY_7/production/result/page.tsx +++ b/frontend/app/(main)/COMPANY_7/production/result/page.tsx @@ -22,6 +22,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen import { Download, Loader2, Inbox, Search, BarChart3, AlertTriangle, CheckCircle2, Package, ChevronDown, ChevronRight, ClipboardList, + ChevronLeft, ChevronsLeft, ChevronsRight, } from "lucide-react"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; @@ -84,6 +85,11 @@ export default function ProductionResultPage() { const [groupBy, setGroupBy] = useState("none"); const [expandedGroups, setExpandedGroups] = useState>(new Set()); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // ── 우측: 실적 ── const [rightTab, setRightTab] = useState<"result" | "defect">("result"); const [processData, setProcessData] = useState([]); @@ -231,6 +237,55 @@ export default function ProductionResultPage() { return result; }, [wiList, groupBy]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(wiList.length / 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]); + + const paginatedGroupedData = useMemo(() => { + if (groupBy === "none") return paginatedRows; + const groups = new Map(); + for (const wi of paginatedRows) { + let key = wi[groupBy] || "미지정"; + if (groupBy === "progress_status") key = PROGRESS_LABEL[key] || key; + else if (groupBy === "work_team" || groupBy === "status") key = resolveCategory(groupBy, key) || key; + if (!groups.has(key)) groups.set(key, []); + groups.get(key)!.push(wi); + } + const result: any[] = []; + groups.forEach((items, gk) => { + result.push({ _group: true, _key: gk, _count: items.length }); + result.push(...items); + }); + return result; + }, [paginatedRows, groupBy]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [wiList.length]); + const toggleGroup = (key: string) => { setExpandedGroups((prev) => { const next = new Set(prev); @@ -337,7 +392,7 @@ export default function ProductionResultPage() { - {groupedData.map((row, idx) => { + {paginatedGroupedData.map((row, idx) => { if (row._group) { const expanded = expandedGroups.has(row._key); return ( @@ -398,6 +453,85 @@ export default function ProductionResultPage() { )}
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {wiList.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
diff --git a/frontend/app/(main)/COMPANY_7/sales/order/page.tsx b/frontend/app/(main)/COMPANY_7/sales/order/page.tsx index 76d6a061..4d006b77 100644 --- a/frontend/app/(main)/COMPANY_7/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_7/sales/order/page.tsx @@ -159,6 +159,11 @@ export default function SalesOrderPage() { const [loading, setLoading] = useState(false); const [totalCount, setTotalCount] = useState(0); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 (DynamicSearchFilter에서 관리) const [searchFilters, setSearchFilters] = useState([]); @@ -427,6 +432,37 @@ export default function SalesOrderPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredFlatRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredFlatRows.slice(start, start + pageSize); + }, [filteredFlatRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredFlatRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -931,8 +967,8 @@ export default function SalesOrderPage() {
{/* 데이터 테이블 (플랫 리스트) */} -
-
+
+
@@ -1013,7 +1049,7 @@ export default function SalesOrderPage() { ) : ( - ts.groupData(filteredFlatRows).map((row: any) => { + ts.groupData(paginatedRows).map((row: any) => { // 그룹 요약 행 렌더링 if (row._isGroupSummary) { return ( @@ -1073,6 +1109,85 @@ export default function SalesOrderPage() {
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredFlatRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 수주 등록/수정 모달 */} diff --git a/frontend/app/(main)/COMPANY_8/logistics/outbound/page.tsx b/frontend/app/(main)/COMPANY_8/logistics/outbound/page.tsx index 457e2723..27a62198 100644 --- a/frontend/app/(main)/COMPANY_8/logistics/outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_8/logistics/outbound/page.tsx @@ -229,6 +229,11 @@ export default function OutboundPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -397,6 +402,37 @@ export default function OutboundPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -848,7 +884,7 @@ export default function OutboundPage() {
-
+
@@ -930,7 +966,7 @@ export default function OutboundPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 출고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_8/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_8/logistics/receiving/page.tsx index 77d21e7b..103affd8 100644 --- a/frontend/app/(main)/COMPANY_8/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_8/logistics/receiving/page.tsx @@ -259,6 +259,11 @@ export default function ReceivingPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -430,6 +435,37 @@ export default function ReceivingPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -878,8 +914,8 @@ export default function ReceivingPage() { /> {/* 입고 목록 테이블 (플랫 리스트) */} -
-
+
+

입고 목록

@@ -891,7 +927,7 @@ export default function ReceivingPage() {
-
+
@@ -973,7 +1009,7 @@ export default function ReceivingPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 입고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_8/production/result/page.tsx b/frontend/app/(main)/COMPANY_8/production/result/page.tsx index 294e722b..b2176781 100644 --- a/frontend/app/(main)/COMPANY_8/production/result/page.tsx +++ b/frontend/app/(main)/COMPANY_8/production/result/page.tsx @@ -22,6 +22,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen import { Download, Loader2, Inbox, Search, BarChart3, AlertTriangle, CheckCircle2, Package, ChevronDown, ChevronRight, ClipboardList, + ChevronLeft, ChevronsLeft, ChevronsRight, } from "lucide-react"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; @@ -84,6 +85,11 @@ export default function ProductionResultPage() { const [groupBy, setGroupBy] = useState("none"); const [expandedGroups, setExpandedGroups] = useState>(new Set()); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // ── 우측: 실적 ── const [rightTab, setRightTab] = useState<"result" | "defect">("result"); const [processData, setProcessData] = useState([]); @@ -231,6 +237,55 @@ export default function ProductionResultPage() { return result; }, [wiList, groupBy]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(wiList.length / 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]); + + const paginatedGroupedData = useMemo(() => { + if (groupBy === "none") return paginatedRows; + const groups = new Map(); + for (const wi of paginatedRows) { + let key = wi[groupBy] || "미지정"; + if (groupBy === "progress_status") key = PROGRESS_LABEL[key] || key; + else if (groupBy === "work_team" || groupBy === "status") key = resolveCategory(groupBy, key) || key; + if (!groups.has(key)) groups.set(key, []); + groups.get(key)!.push(wi); + } + const result: any[] = []; + groups.forEach((items, gk) => { + result.push({ _group: true, _key: gk, _count: items.length }); + result.push(...items); + }); + return result; + }, [paginatedRows, groupBy]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [wiList.length]); + const toggleGroup = (key: string) => { setExpandedGroups((prev) => { const next = new Set(prev); @@ -337,7 +392,7 @@ export default function ProductionResultPage() { - {groupedData.map((row, idx) => { + {paginatedGroupedData.map((row, idx) => { if (row._group) { const expanded = expandedGroups.has(row._key); return ( @@ -398,6 +453,85 @@ export default function ProductionResultPage() { )}
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {wiList.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
diff --git a/frontend/app/(main)/COMPANY_8/sales/order/page.tsx b/frontend/app/(main)/COMPANY_8/sales/order/page.tsx index 76d6a061..4d006b77 100644 --- a/frontend/app/(main)/COMPANY_8/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_8/sales/order/page.tsx @@ -159,6 +159,11 @@ export default function SalesOrderPage() { const [loading, setLoading] = useState(false); const [totalCount, setTotalCount] = useState(0); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 (DynamicSearchFilter에서 관리) const [searchFilters, setSearchFilters] = useState([]); @@ -427,6 +432,37 @@ export default function SalesOrderPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredFlatRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredFlatRows.slice(start, start + pageSize); + }, [filteredFlatRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredFlatRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -931,8 +967,8 @@ export default function SalesOrderPage() {
{/* 데이터 테이블 (플랫 리스트) */} -
-
+
+
@@ -1013,7 +1049,7 @@ export default function SalesOrderPage() { ) : ( - ts.groupData(filteredFlatRows).map((row: any) => { + ts.groupData(paginatedRows).map((row: any) => { // 그룹 요약 행 렌더링 if (row._isGroupSummary) { return ( @@ -1073,6 +1109,85 @@ export default function SalesOrderPage() {
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredFlatRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 수주 등록/수정 모달 */} diff --git a/frontend/app/(main)/COMPANY_9/logistics/outbound/page.tsx b/frontend/app/(main)/COMPANY_9/logistics/outbound/page.tsx index 457e2723..27a62198 100644 --- a/frontend/app/(main)/COMPANY_9/logistics/outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_9/logistics/outbound/page.tsx @@ -229,6 +229,11 @@ export default function OutboundPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -397,6 +402,37 @@ export default function OutboundPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -848,7 +884,7 @@ export default function OutboundPage() {
-
+
@@ -930,7 +966,7 @@ export default function OutboundPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 출고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_9/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_9/logistics/receiving/page.tsx index 77d21e7b..103affd8 100644 --- a/frontend/app/(main)/COMPANY_9/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_9/logistics/receiving/page.tsx @@ -259,6 +259,11 @@ export default function ReceivingPage() { const [loading, setLoading] = useState(false); const [checkedIds, setCheckedIds] = useState([]); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 const [searchFilters, setSearchFilters] = useState([]); @@ -430,6 +435,37 @@ export default function ReceivingPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredRows.slice(start, start + pageSize); + }, [filteredRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -878,8 +914,8 @@ export default function ReceivingPage() { /> {/* 입고 목록 테이블 (플랫 리스트) */} -
-
+
+

입고 목록

@@ -891,7 +927,7 @@ export default function ReceivingPage() {
-
+
@@ -973,7 +1009,7 @@ export default function ReceivingPage() { ) : ( - filteredRows.map((row) => { + paginatedRows.map((row) => { const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 입고 등록 모달 */} diff --git a/frontend/app/(main)/COMPANY_9/production/result/page.tsx b/frontend/app/(main)/COMPANY_9/production/result/page.tsx index 294e722b..b2176781 100644 --- a/frontend/app/(main)/COMPANY_9/production/result/page.tsx +++ b/frontend/app/(main)/COMPANY_9/production/result/page.tsx @@ -22,6 +22,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen import { Download, Loader2, Inbox, Search, BarChart3, AlertTriangle, CheckCircle2, Package, ChevronDown, ChevronRight, ClipboardList, + ChevronLeft, ChevronsLeft, ChevronsRight, } from "lucide-react"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; @@ -84,6 +85,11 @@ export default function ProductionResultPage() { const [groupBy, setGroupBy] = useState("none"); const [expandedGroups, setExpandedGroups] = useState>(new Set()); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // ── 우측: 실적 ── const [rightTab, setRightTab] = useState<"result" | "defect">("result"); const [processData, setProcessData] = useState([]); @@ -231,6 +237,55 @@ export default function ProductionResultPage() { return result; }, [wiList, groupBy]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(wiList.length / 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]); + + const paginatedGroupedData = useMemo(() => { + if (groupBy === "none") return paginatedRows; + const groups = new Map(); + for (const wi of paginatedRows) { + let key = wi[groupBy] || "미지정"; + if (groupBy === "progress_status") key = PROGRESS_LABEL[key] || key; + else if (groupBy === "work_team" || groupBy === "status") key = resolveCategory(groupBy, key) || key; + if (!groups.has(key)) groups.set(key, []); + groups.get(key)!.push(wi); + } + const result: any[] = []; + groups.forEach((items, gk) => { + result.push({ _group: true, _key: gk, _count: items.length }); + result.push(...items); + }); + return result; + }, [paginatedRows, groupBy]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [wiList.length]); + const toggleGroup = (key: string) => { setExpandedGroups((prev) => { const next = new Set(prev); @@ -337,7 +392,7 @@ export default function ProductionResultPage() { - {groupedData.map((row, idx) => { + {paginatedGroupedData.map((row, idx) => { if (row._group) { const expanded = expandedGroups.has(row._key); return ( @@ -398,6 +453,85 @@ export default function ProductionResultPage() { )}
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {wiList.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
diff --git a/frontend/app/(main)/COMPANY_9/sales/order/page.tsx b/frontend/app/(main)/COMPANY_9/sales/order/page.tsx index 8f137c7a..4d006b77 100644 --- a/frontend/app/(main)/COMPANY_9/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_9/sales/order/page.tsx @@ -159,6 +159,11 @@ export default function SalesOrderPage() { const [loading, setLoading] = useState(false); const [totalCount, setTotalCount] = useState(0); + // 페이지네이션 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [pageSizeInput, setPageSizeInput] = useState("20"); + // 검색 필터 (DynamicSearchFilter에서 관리) const [searchFilters, setSearchFilters] = useState([]); @@ -427,6 +432,37 @@ export default function SalesOrderPage() { return rows; }, [flatRows, headerFilters, sortState]); + // 페이지네이션 계산 + const totalPages = Math.max(1, Math.ceil(filteredFlatRows.length / pageSize)); + const safePage = Math.min(Math.max(1, currentPage), totalPages); + const paginatedRows = useMemo(() => { + const start = (safePage - 1) * pageSize; + return filteredFlatRows.slice(start, start + pageSize); + }, [filteredFlatRows, safePage, pageSize]); + + 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 pages; + }; + + // 필터 변경 시 첫 페이지로 이동 + useEffect(() => { setCurrentPage(1); }, [headerFilters, sortState, filteredFlatRows.length]); + // 헤더 필터 토글/초기화 const toggleHeaderFilter = (colKey: string, value: string) => { setHeaderFilters((prev) => { @@ -931,8 +967,8 @@ export default function SalesOrderPage() {
{/* 데이터 테이블 (플랫 리스트) */} -
-
+
+
@@ -1013,7 +1049,19 @@ export default function SalesOrderPage() { ) : ( - filteredFlatRows.map((row) => { + ts.groupData(paginatedRows).map((row: any) => { + // 그룹 요약 행 렌더링 + if (row._isGroupSummary) { + return ( + + + {row._groupLabel || "합계"}: {row._count ? `${row._count}건` : ""} + {row.qty ? ` · 수량 ${Number(row.qty).toLocaleString()}` : ""} + {row.amount ? ` · 금액 ${Number(row.amount).toLocaleString()}` : ""} + + + ); + } const isChecked = checkedIds.includes(row.id); return (
+ + {/* 페이지네이션 */} +
+
+
+ 전체 + {filteredFlatRows.length.toLocaleString()} + +
+
+ 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 === "..." ? ( + ... + ) : ( + + ) + )} + + +
+
+ { + if (e.key === "Enter") { + const val = parseInt((e.target as HTMLInputElement).value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + (e.target as HTMLInputElement).value = ""; + (e.target as HTMLInputElement).blur(); + } + } + }} + onBlur={(e) => { + const val = parseInt(e.target.value, 10); + if (!isNaN(val) && val >= 1 && val <= totalPages) { + setCurrentPage(val); + } + e.target.value = ""; + }} + /> + / {totalPages} 페이지 +
+
{/* 수주 등록/수정 모달 */}