diff --git a/backend-spring/src/main/resources/mapper/externalDbConnection.xml b/backend-spring/src/main/resources/mapper/externalDbConnection.xml index 4dbac711..e0da15c8 100644 --- a/backend-spring/src/main/resources/mapper/externalDbConnection.xml +++ b/backend-spring/src/main/resources/mapper/externalDbConnection.xml @@ -81,7 +81,7 @@ , E.CREATED_DATE , E.UPDATED_DATE FROM EXTERNAL_DB_CONNECTIONS E - WHERE E.ID = #{id} + WHERE E.ID = #{id}::varchar @@ -109,14 +109,14 @@ , CREATED_DATE , UPDATED_DATE FROM EXTERNAL_DB_CONNECTIONS - WHERE ID = #{id} + WHERE ID = #{id}::varchar @@ -134,7 +134,7 @@ FROM EXTERNAL_DB_CONNECTIONS WHERE CONNECTION_NAME = #{connection_name} AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*') - AND ID != #{exclude_id} + AND ID != #{exclude_id}::varchar LIMIT 1 @@ -208,13 +208,13 @@ UPDATED_BY = #{updated_by}, UPDATED_DATE = NOW() - WHERE ID = #{id} + WHERE ID = #{id}::varchar DELETE FROM EXTERNAL_DB_CONNECTIONS - WHERE ID = #{id} + WHERE ID = #{id}::varchar AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*') diff --git a/frontend/app/(main)/admin/automaticMng/batchmngList/page.tsx b/frontend/app/(main)/admin/automaticMng/batchmngList/page.tsx index d73484b7..87b444a5 100644 --- a/frontend/app/(main)/admin/automaticMng/batchmngList/page.tsx +++ b/frontend/app/(main)/admin/automaticMng/batchmngList/page.tsx @@ -35,6 +35,7 @@ import { } from "@/lib/api/batch"; import { ScrollToTop } from "@/components/common/ScrollToTop"; import { CrossTenantBanner } from "@/components/common/CrossTenantBanner"; +import { Pagination } from "@/components/common/Pagination"; import { useTabStore } from "@/stores/tabStore"; function cronToKorean(cron: string): string { @@ -331,6 +332,10 @@ export default function BatchManagementPage() { const [isBatchTypeModalOpen, setIsBatchTypeModalOpen] = useState(false); const [togglingBatch, setTogglingBatch] = useState(null); + // 페이지네이션 상태 + const [currentPage, setCurrentPage] = useState(1); + const [itemsPerPage, setItemsPerPage] = useState(20); + const loadBatchConfigs = useCallback(async () => { setLoading(true); try { @@ -364,6 +369,9 @@ export default function BatchManagementPage() { useEffect(() => { loadBatchConfigs(); }, [loadBatchConfigs]); + // 검색/필터 변경 시 1페이지로 리셋 + useEffect(() => { setCurrentPage(1); }, [searchTerm, statusFilter]); + const handleRowClick = async (batchId: number) => { if (expandedBatch === batchId) { setExpandedBatch(null); return; } setExpandedBatch(batchId); @@ -443,14 +451,22 @@ export default function BatchManagementPage() { return true; }); + // 페이지네이션 계산 + const totalItems = filteredBatches.length; + const totalPages = Math.max(1, Math.ceil(totalItems / itemsPerPage)); + const safePage = Math.min(currentPage, totalPages); + const startIdx = (safePage - 1) * itemsPerPage; + const endIdx = Math.min(startIdx + itemsPerPage, totalItems); + const pagedBatches = filteredBatches.slice(startIdx, endIdx); + const activeBatches = batchConfigs.filter(b => b.is_active === "Y").length; const inactiveBatches = batchConfigs.length - activeBatches; const execDiff = stats ? stats.todayExecutions - stats.prevDayExecutions : 0; const failDiff = stats ? stats.todayFailures - stats.prevDayFailures : 0; return ( -
-
+
+
{/* 헤더 */}
@@ -534,8 +550,8 @@ export default function BatchManagementPage() {
- {/* 배치 리스트 */} -
+ {/* 배치 리스트 - 자체 스크롤 */} +
{loading && batchConfigs.length === 0 && (
@@ -549,7 +565,7 @@ export default function BatchManagementPage() {
)} - {filteredBatches.map((batch) => { + {pagedBatches.map((batch) => { const batchId = batch.id!; const isExpanded = expandedBatch === batchId; const isExecuting = executingBatch === batchId; @@ -674,6 +690,29 @@ export default function BatchManagementPage() { })}
+ {/* 페이지네이션 — 리스트 영역 아래 고정 */} + {!loading && ( +
+ { + setItemsPerPage(size); + setCurrentPage(1); + }} + showPageSizeSelector + pageSizeOptions={[10, 20, 50, 100]} + /> +
+ )} + {/* 배치 타입 선택 모달 */} {isBatchTypeModalOpen && (
setIsBatchTypeModalOpen(false)}> diff --git a/frontend/app/(main)/admin/automaticMng/exconList/page.tsx b/frontend/app/(main)/admin/automaticMng/exconList/page.tsx index 89ecba46..15c2dbda 100644 --- a/frontend/app/(main)/admin/automaticMng/exconList/page.tsx +++ b/frontend/app/(main)/admin/automaticMng/exconList/page.tsx @@ -231,15 +231,15 @@ export default function ExternalConnectionsPage() { ) }, { key: "id", label: "연결 테스트", width: "150px", hideOnMobile: true, render: (_v, row) => ( -
+
{testResults.has(row.id!) && ( - + {testResults.get(row.id!) ? "성공" : "실패"} )} @@ -264,68 +264,68 @@ export default function ExternalConnectionsPage() { ]; return ( -
-
+
+
{/* 페이지 헤더 */} -
-

외부 커넥션 관리

-

외부 데이터베이스 및 REST API 연결 정보를 관리합니다

+
+

외부 커넥션 관리

+

외부 데이터베이스 및 REST API 연결 정보를 관리합니다

{/* 탭 */} - setActiveTab(value as ConnectionTabType)}> - - - + setActiveTab(value as ConnectionTabType)} className="flex min-h-0 flex-1 flex-col gap-3"> + + + 데이터베이스 연결 - - + + REST API 연결 {/* 데이터베이스 연결 탭 */} - + {/* 검색 및 필터 */} -
-
-
- +
+
+
+ setSearchTerm(e.target.value)} - className="h-10 pl-10 text-sm" + className="h-8 pl-9 text-xs" />
-
@@ -338,10 +338,12 @@ export default function ExternalConnectionsPage() { isLoading={loading} emptyMessage="등록된 연결이 없습니다" skeletonCount={5} + compact + scrollContainer cardTitle={(c) => c.connection_name} cardSubtitle={(c) => {c.host}:{c.port}/{c.database_name}} cardHeaderRight={(c) => ( - + {c.is_active === "Y" ? "활성" : "비활성"} )} @@ -351,7 +353,7 @@ export default function ExternalConnectionsPage() { @@ -436,7 +438,7 @@ export default function ExternalConnectionsPage() { {/* REST API 연결 탭 */} - + diff --git a/frontend/components/admin/RestApiConnectionList.tsx b/frontend/components/admin/RestApiConnectionList.tsx index 8ed5ea58..edd2ced1 100644 --- a/frontend/components/admin/RestApiConnectionList.tsx +++ b/frontend/components/admin/RestApiConnectionList.tsx @@ -219,27 +219,27 @@ export function RestApiConnectionList() { return ( <> {/* 검색 및 필터 */} -
-
+
+
{/* 검색 */} -
- +
+ setSearchTerm(e.target.value)} - className="h-10 pl-10 text-sm" + className="h-8 pl-9 text-xs" />
{/* 인증 타입 필터 */} - + {ACTIVE_STATUS_OPTIONS.map((option) => ( - + {option.label} ))} @@ -262,79 +262,79 @@ export function RestApiConnectionList() {
{/* 추가 버튼 */} -
{/* 연결 목록 */} {loading ? ( -
-
로딩 중...
+
+
로딩 중...
) : connections.length === 0 ? ( -
+
-

등록된 REST API 연결이 없습니다

+

등록된 REST API 연결이 없습니다

) : ( -
- - - - 연결명 - 회사 - 기본 URL - 인증 타입 - 헤더 수 - 상태 - 마지막 테스트 - 연결 테스트 - 작업 +
+
+ + + 연결명 + 회사 + 기본 URL + 인증 타입 + 헤더 수 + 상태 + 마지막 테스트 + 연결 테스트 + 작업 {connections.map((connection) => ( - - + +
{connection.connection_name}
{connection.description && ( -
+
{connection.description}
)}
- + {(connection as any).company_name || connection.company_code} - +
{connection.base_url}
- - {AUTH_TYPE_LABELS[connection.auth_type] || connection.auth_type} + + {AUTH_TYPE_LABELS[connection.auth_type] || connection.auth_type} - + {Object.keys(connection.default_headers || {}).length} - - + + {connection.is_active === "Y" ? "활성" : "비활성"} - + {connection.last_test_date ? ( -
-
{new Date(connection.last_test_date).toLocaleDateString()}
+
+ {new Date(connection.last_test_date).toLocaleDateString()} {connection.last_test_result === "Y" ? "성공" : "실패"} @@ -343,41 +343,41 @@ export function RestApiConnectionList() { - )} - -
+ +
{testResults.has(connection.id!) && ( - + {testResults.get(connection.id!) ? "성공" : "실패"} )}
- -
+ +
diff --git a/frontend/components/common/ResponsiveDataView.tsx b/frontend/components/common/ResponsiveDataView.tsx index 3cd8f723..4f5790d7 100644 --- a/frontend/components/common/ResponsiveDataView.tsx +++ b/frontend/components/common/ResponsiveDataView.tsx @@ -92,6 +92,11 @@ export function ResponsiveDataView({ }: ResponsiveDataViewProps) { const rowHeight = compact ? "h-10" : "h-16"; const headHeight = compact ? "h-9" : "h-12"; + const bodyText = compact ? "text-xs" : "text-sm"; + const headText = compact ? "text-xs" : "text-sm"; + const cellPad = compact ? "px-3" : ""; + const cardTitleClass = compact ? "text-sm" : "text-base"; + const cardSubText = compact ? "text-xs" : "text-sm"; // cardFields 미지정 시 columns에서 자동 생성 function resolveCardFields(item: T): RDVCardField[] { if (typeof cardFields === "function") return cardFields(item); @@ -233,16 +238,20 @@ export function ResponsiveDataView({ {/* 데스크톱 테이블 (컨테이너 ≥ 48rem / 768px) */}
-
+
@@ -250,7 +259,7 @@ export function ResponsiveDataView({ {col.label} @@ -258,7 +267,7 @@ export function ResponsiveDataView({ {renderActions && ( {actionsLabel || "작업"} @@ -278,7 +287,7 @@ export function ResponsiveDataView({ {columns.map((col) => ( {col.render ? col.render(getNestedValue(item, col.key), item, index) @@ -286,7 +295,7 @@ export function ResponsiveDataView({ ))} {renderActions && ( - +
{renderActions(item)}
)} @@ -319,11 +328,11 @@ export function ResponsiveDataView({ {/* 카드 헤더 */}
-

+

{cardTitle(item)}

{cardSubtitle && ( -

+

{cardSubtitle(item)}

)} @@ -337,7 +346,7 @@ export function ResponsiveDataView({ {fields.length > 0 && (
{fields.map((field, i) => ( -
+
{field.label}