페이지네이션 기능 개선 및 공통화
- 페이지네이션 로직을 별도 JS 파일로 분리 (public/js/pagination.js)
* 쿠키 기반 per_page 값 저장 및 유지
* 페이지네이션 이벤트 핸들러 통합
* 중복 코드 제거
- 페이지네이션 UI 개선
* 처음으로/끝으로 이동 버튼 추가
* selectbox 너비 조정 (90px)
* 서버사이드 selected 속성으로 옵션 매칭 개선
- 초기 페이지당 항목 수를 10개로 변경
* TenantController, UserController, DepartmentController, RoleController 기본값 수정
- layouts/app.blade.php
* pagination.js 로드 추가
* 기존 인라인 스크립트 제거
변경 파일:
- public/js/pagination.js (신규)
- resources/views/layouts/app.blade.php
- resources/views/partials/pagination.blade.php
- app/Http/Controllers/Api/Admin/{Tenant,User,Department,Role}Controller.php
This commit is contained in:
@@ -22,7 +22,7 @@ public function index(Request $request): JsonResponse
|
||||
{
|
||||
$departments = $this->departmentService->getDepartments(
|
||||
$request->all(),
|
||||
$request->integer('per_page', 15)
|
||||
$request->integer('per_page', 10)
|
||||
);
|
||||
|
||||
// HTMX 요청 시 HTML 반환
|
||||
|
||||
@@ -22,7 +22,7 @@ public function index(Request $request): JsonResponse
|
||||
{
|
||||
$roles = $this->roleService->getRoles(
|
||||
$request->all(),
|
||||
$request->integer('per_page', 15)
|
||||
$request->integer('per_page', 10)
|
||||
);
|
||||
|
||||
// HTMX 요청 시 HTML 반환
|
||||
|
||||
@@ -22,7 +22,7 @@ public function index(Request $request): JsonResponse
|
||||
{
|
||||
$tenants = $this->tenantService->getTenants(
|
||||
$request->all(),
|
||||
$request->integer('per_page', 15)
|
||||
$request->integer('per_page', 10)
|
||||
);
|
||||
|
||||
// HTMX 요청 시 HTML 반환
|
||||
|
||||
@@ -22,7 +22,7 @@ public function index(Request $request): JsonResponse
|
||||
{
|
||||
$users = $this->userService->getUsers(
|
||||
$request->all(),
|
||||
$request->integer('per_page', 15)
|
||||
$request->integer('per_page', 10)
|
||||
);
|
||||
|
||||
// HTMX 요청인 경우 HTML 반환
|
||||
|
||||
126
public/js/pagination.js
Normal file
126
public/js/pagination.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 공통 페이지네이션 스크립트
|
||||
*
|
||||
* 기능:
|
||||
* - 쿠키 기반 per_page 값 관리
|
||||
* - 페이지네이션 이벤트 핸들러
|
||||
* - HTMX 연동
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// 쿠키 헬퍼 함수
|
||||
// ============================================
|
||||
|
||||
window.setCookie = function(name, value, days = 365) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
const expires = "expires=" + date.toUTCString();
|
||||
document.cookie = name + "=" + value + ";" + expires + ";path=/";
|
||||
};
|
||||
|
||||
window.getCookie = function(name) {
|
||||
const nameEQ = name + "=";
|
||||
const ca = document.cookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
window.getPerPageFromCookie = function() {
|
||||
const savedPerPage = getCookie('pagination_per_page');
|
||||
return savedPerPage ? savedPerPage : '10'; // 기본값 10
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 페이지네이션 이벤트 핸들러
|
||||
// ============================================
|
||||
|
||||
// 페이지당 항목 수 변경 핸들러
|
||||
window.handlePerPageChange = function(perPage) {
|
||||
console.log('handlePerPageChange called with:', perPage);
|
||||
|
||||
// 쿠키에 저장
|
||||
setCookie('pagination_per_page', perPage);
|
||||
console.log('Cookie saved. Reading back:', getCookie('pagination_per_page'));
|
||||
|
||||
// 현재 페이지의 HTMX 타겟 찾기
|
||||
const target = document.querySelector('[hx-trigger*="filterSubmit"]');
|
||||
if (target) {
|
||||
const perPageInput = document.getElementById('perPageInput');
|
||||
const pageInput = document.getElementById('pageInput');
|
||||
|
||||
if (perPageInput && pageInput) {
|
||||
perPageInput.value = perPage;
|
||||
pageInput.value = 1; // 페이지를 1로 초기화
|
||||
console.log('Triggering HTMX with per_page:', perPageInput.value);
|
||||
htmx.trigger(target, 'filterSubmit');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 페이지 변경 핸들러
|
||||
window.handlePageChange = function(page) {
|
||||
const target = document.querySelector('[hx-trigger*="filterSubmit"]');
|
||||
if (target) {
|
||||
const pageInput = document.getElementById('pageInput');
|
||||
|
||||
if (pageInput) {
|
||||
pageInput.value = page;
|
||||
htmx.trigger(target, 'filterSubmit');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 초기화
|
||||
// ============================================
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// filterForm 찾기
|
||||
const filterForm = document.getElementById('filterForm');
|
||||
if (!filterForm) return;
|
||||
|
||||
// hidden input이 이미 존재하는지 확인
|
||||
let perPageInput = document.getElementById('perPageInput');
|
||||
let pageInput = document.getElementById('pageInput');
|
||||
|
||||
// per_page input 생성 또는 업데이트
|
||||
if (!perPageInput) {
|
||||
perPageInput = document.createElement('input');
|
||||
perPageInput.type = 'hidden';
|
||||
perPageInput.name = 'per_page';
|
||||
perPageInput.id = 'perPageInput';
|
||||
filterForm.appendChild(perPageInput);
|
||||
}
|
||||
perPageInput.value = getPerPageFromCookie();
|
||||
|
||||
// page input 생성 또는 업데이트
|
||||
if (!pageInput) {
|
||||
pageInput = document.createElement('input');
|
||||
pageInput.type = 'hidden';
|
||||
pageInput.name = 'page';
|
||||
pageInput.id = 'pageInput';
|
||||
pageInput.value = '1';
|
||||
filterForm.appendChild(pageInput);
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// HTMX 이벤트 핸들러
|
||||
// ============================================
|
||||
|
||||
// HTMX afterSwap: 테이블 새로고침 시 selectbox 재설정
|
||||
document.body.addEventListener('htmx:afterSwap', function(event) {
|
||||
// selectbox 설정을 약간 지연시켜 DOM이 완전히 렌더링된 후 실행
|
||||
setTimeout(function() {
|
||||
const perPageSelect = document.getElementById('perPageSelect');
|
||||
if (perPageSelect) {
|
||||
const savedPerPage = getPerPageFromCookie();
|
||||
console.log('HTMX afterSwap - Setting selectbox to:', savedPerPage);
|
||||
perPageSelect.value = savedPerPage;
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
@@ -7,6 +7,7 @@
|
||||
<title>@yield('title', 'Dashboard') - {{ config('app.name') }}</title>
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||
<script src="{{ asset('js/pagination.js') }}"></script>
|
||||
@stack('styles')
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
|
||||
@@ -18,18 +18,16 @@
|
||||
이전
|
||||
</span>
|
||||
@else
|
||||
<button hx-get="{{ $paginator->previousPageUrl() }}"
|
||||
hx-target="{{ $target }}"
|
||||
hx-include="{{ $includeForm ?? '' }}"
|
||||
<button type="button"
|
||||
onclick="handlePageChange({{ $paginator->currentPage() - 1 }})"
|
||||
class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
|
||||
이전
|
||||
</button>
|
||||
@endif
|
||||
|
||||
@if($paginator->hasMorePages())
|
||||
<button hx-get="{{ $paginator->nextPageUrl() }}"
|
||||
hx-target="{{ $target }}"
|
||||
hx-include="{{ $includeForm ?? '' }}"
|
||||
<button type="button"
|
||||
onclick="handlePageChange({{ $paginator->currentPage() + 1 }})"
|
||||
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
|
||||
다음
|
||||
</button>
|
||||
@@ -42,29 +40,53 @@ class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 t
|
||||
|
||||
<!-- 데스크톱 네비게이션 -->
|
||||
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<div class="flex items-center gap-4">
|
||||
<p class="text-sm text-gray-700">
|
||||
전체 <span class="font-medium">{{ $paginator->total() }}</span>개 중
|
||||
<span class="font-medium">{{ $paginator->firstItem() }}</span>
|
||||
~
|
||||
<span class="font-medium">{{ $paginator->lastItem() }}</span>
|
||||
</p>
|
||||
<!-- 페이지당 항목 수 선택 -->
|
||||
<select name="per_page" id="perPageSelect"
|
||||
style="min-width: 90px;"
|
||||
class="px-3 py-1 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
onchange="handlePerPageChange(this.value)">
|
||||
<option value="10" {{ $paginator->perPage() == 10 ? 'selected' : '' }}>10개씩</option>
|
||||
<option value="20" {{ $paginator->perPage() == 20 ? 'selected' : '' }}>20개씩</option>
|
||||
<option value="30" {{ $paginator->perPage() == 30 ? 'selected' : '' }}>30개씩</option>
|
||||
<option value="50" {{ $paginator->perPage() == 50 ? 'selected' : '' }}>50개씩</option>
|
||||
<option value="100" {{ $paginator->perPage() == 100 ? 'selected' : '' }}>100개씩</option>
|
||||
<option value="200" {{ $paginator->perPage() == 200 ? 'selected' : '' }}>200개씩</option>
|
||||
<option value="500" {{ $paginator->perPage() == 500 ? 'selected' : '' }}>500개씩</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
|
||||
<!-- 처음으로 버튼 -->
|
||||
@if ($paginator->onFirstPage())
|
||||
<span class="relative inline-flex items-center px-3 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">
|
||||
처음
|
||||
</span>
|
||||
@else
|
||||
<button type="button"
|
||||
onclick="handlePageChange(1)"
|
||||
class="relative inline-flex items-center px-3 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
||||
처음
|
||||
</button>
|
||||
@endif
|
||||
|
||||
<!-- 이전 버튼 -->
|
||||
@if ($paginator->onFirstPage())
|
||||
<span class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">
|
||||
<span class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</span>
|
||||
@else
|
||||
<button hx-get="{{ $paginator->previousPageUrl() }}"
|
||||
hx-target="{{ $target }}"
|
||||
hx-include="{{ $includeForm ?? '' }}"
|
||||
hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'
|
||||
class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
||||
<button type="button"
|
||||
onclick="handlePageChange({{ $paginator->currentPage() - 1 }})"
|
||||
class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
@@ -72,16 +94,29 @@ class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gr
|
||||
@endif
|
||||
|
||||
<!-- 페이지 번호 -->
|
||||
@foreach ($paginator->getUrlRange(1, $paginator->lastPage()) as $page => $url)
|
||||
@php
|
||||
$currentPage = $paginator->currentPage();
|
||||
$lastPage = $paginator->lastPage();
|
||||
$maxPages = 10;
|
||||
|
||||
// 시작 페이지 계산
|
||||
$startPage = max(1, $currentPage - floor($maxPages / 2));
|
||||
$endPage = min($lastPage, $startPage + $maxPages - 1);
|
||||
|
||||
// 끝에서 10개를 채우지 못하면 시작 페이지 조정
|
||||
if ($endPage - $startPage + 1 < $maxPages) {
|
||||
$startPage = max(1, $endPage - $maxPages + 1);
|
||||
}
|
||||
@endphp
|
||||
|
||||
@foreach ($paginator->getUrlRange($startPage, $endPage) as $page => $url)
|
||||
@if ($page == $paginator->currentPage())
|
||||
<span class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-blue-50 text-sm font-medium text-blue-600">
|
||||
{{ $page }}
|
||||
</span>
|
||||
@else
|
||||
<button hx-get="{{ $url }}"
|
||||
hx-target="{{ $target }}"
|
||||
hx-include="{{ $includeForm ?? '' }}"
|
||||
hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'
|
||||
<button type="button"
|
||||
onclick="handlePageChange({{ $page }})"
|
||||
class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">
|
||||
{{ $page }}
|
||||
</button>
|
||||
@@ -90,22 +125,33 @@ class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-whi
|
||||
|
||||
<!-- 다음 버튼 -->
|
||||
@if ($paginator->hasMorePages())
|
||||
<button hx-get="{{ $paginator->nextPageUrl() }}"
|
||||
hx-target="{{ $target }}"
|
||||
hx-include="{{ $includeForm ?? '' }}"
|
||||
hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'
|
||||
class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
||||
<button type="button"
|
||||
onclick="handlePageChange({{ $paginator->currentPage() + 1 }})"
|
||||
class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
@else
|
||||
<span class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">
|
||||
<span class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</span>
|
||||
@endif
|
||||
|
||||
<!-- 끝으로 버튼 -->
|
||||
@if ($paginator->hasMorePages())
|
||||
<button type="button"
|
||||
onclick="handlePageChange({{ $paginator->lastPage() }})"
|
||||
class="relative inline-flex items-center px-3 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
||||
끝
|
||||
</button>
|
||||
@else
|
||||
<span class="relative inline-flex items-center px-3 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-300 cursor-not-allowed">
|
||||
끝
|
||||
</span>
|
||||
@endif
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user