fix: [archived-records] 페이지네이션 오류 수정
- GROUP BY + paginate() 조합 시 total count 오류 해결 (서브쿼리 방식) - DB::table() 사용 시 page 파라미터 명시적 전달 - 커스텀 페이지네이션을 공용 컴포넌트(partials.pagination)로 교체 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -12,12 +12,14 @@ class ArchivedRecordService
|
||||
/**
|
||||
* 아카이브 레코드 목록 조회 (batch_id로 그룹핑, 페이지네이션)
|
||||
* batch_id가 NULL인 기존 데이터는 각각 개별 batch로 취급
|
||||
*
|
||||
* NOTE: GROUP BY + paginate() 조합 시 total count가 잘못 계산되는 문제 해결
|
||||
* 서브쿼리로 GROUP BY 결과를 감싼 후 외부에서 paginate() 실행
|
||||
*/
|
||||
public function getArchivedRecordsBatched(array $filters = [], int $perPage = 15): LengthAwarePaginator
|
||||
{
|
||||
// batch_id별로 그룹핑하여 첫 번째 레코드만 조회
|
||||
// batch_id가 NULL인 경우 id를 기반으로 가상 batch_id 생성
|
||||
$query = ArchivedRecord::query()
|
||||
// 1. 서브쿼리: batch_id별 그룹핑
|
||||
$subQuery = ArchivedRecord::query()
|
||||
->select([
|
||||
DB::raw("COALESCE(batch_id, CONCAT('legacy_', id)) as batch_id"),
|
||||
DB::raw("COALESCE(batch_description, CONCAT(record_type, ' 삭제 (ID: ', original_id, ')')) as batch_description"),
|
||||
@@ -27,22 +29,29 @@ public function getArchivedRecordsBatched(array $filters = [], int $perPage = 15
|
||||
DB::raw('MIN(deleted_by) as deleted_by'),
|
||||
DB::raw('MIN(deleted_at) as deleted_at'),
|
||||
])
|
||||
->groupBy(DB::raw("COALESCE(batch_id, CONCAT('legacy_', id))"), DB::raw("COALESCE(batch_description, CONCAT(record_type, ' 삭제 (ID: ', original_id, ')'))"));
|
||||
->groupBy(
|
||||
DB::raw("COALESCE(batch_id, CONCAT('legacy_', id))"),
|
||||
DB::raw("COALESCE(batch_description, CONCAT(record_type, ' 삭제 (ID: ', original_id, ')'))")
|
||||
);
|
||||
|
||||
// 2. 외부 쿼리: 서브쿼리를 감싸서 정확한 count 계산
|
||||
$query = DB::table(DB::raw("({$subQuery->toSql()}) as grouped"))
|
||||
->mergeBindings($subQuery->getQuery())
|
||||
->select('*');
|
||||
|
||||
// 레코드 타입 필터
|
||||
if (! empty($filters['record_type'])) {
|
||||
$query->having('record_types', 'like', "%{$filters['record_type']}%");
|
||||
$query->where('record_types', 'like', "%{$filters['record_type']}%");
|
||||
}
|
||||
|
||||
// 삭제자 필터
|
||||
if (! empty($filters['deleted_by'])) {
|
||||
$query->having('deleted_by', $filters['deleted_by']);
|
||||
$query->where('deleted_by', $filters['deleted_by']);
|
||||
}
|
||||
|
||||
// 검색
|
||||
if (! empty($filters['search'])) {
|
||||
$search = $filters['search'];
|
||||
$query->where('batch_description', 'like', "%{$search}%");
|
||||
$query->where('batch_description', 'like', "%{$filters['search']}%");
|
||||
}
|
||||
|
||||
// 정렬 (기본: 삭제일시 내림차순)
|
||||
@@ -50,7 +59,11 @@ public function getArchivedRecordsBatched(array $filters = [], int $perPage = 15
|
||||
$sortDirection = $filters['sort_direction'] ?? 'desc';
|
||||
$query->orderBy($sortBy, $sortDirection);
|
||||
|
||||
return $query->paginate($perPage);
|
||||
// NOTE: DB::table() 사용 시 request의 page 파라미터를 자동으로 읽지 못함
|
||||
// filters에서 page를 명시적으로 전달
|
||||
$page = $filters['page'] ?? null;
|
||||
|
||||
return $query->paginate($perPage, ['*'], 'page', $page);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,57 +74,9 @@ class="text-blue-600 hover:text-blue-900 transition"
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- 페이지네이션 --}}
|
||||
@if($records->hasPages())
|
||||
<div class="px-6 py-4 border-t border-gray-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-sm text-gray-700">
|
||||
총 <span class="font-medium">{{ $records->total() }}</span>개 작업 중
|
||||
<span class="font-medium">{{ $records->firstItem() }}</span> -
|
||||
<span class="font-medium">{{ $records->lastItem() }}</span>개 표시
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
{{-- 이전 페이지 --}}
|
||||
@if($records->onFirstPage())
|
||||
<span class="px-3 py-1 text-sm text-gray-400 bg-gray-100 rounded cursor-not-allowed">이전</span>
|
||||
@else
|
||||
<button type="button"
|
||||
class="px-3 py-1 text-sm text-gray-700 bg-white border border-gray-300 rounded hover:bg-gray-50"
|
||||
hx-get="/api/admin/archived-records?page={{ $records->currentPage() - 1 }}"
|
||||
hx-target="#archived-record-table"
|
||||
hx-include="#filterForm">
|
||||
이전
|
||||
</button>
|
||||
@endif
|
||||
|
||||
{{-- 페이지 번호 --}}
|
||||
@foreach(range(max(1, $records->currentPage() - 2), min($records->lastPage(), $records->currentPage() + 2)) as $page)
|
||||
@if($page == $records->currentPage())
|
||||
<span class="px-3 py-1 text-sm text-white bg-blue-600 rounded">{{ $page }}</span>
|
||||
@else
|
||||
<button type="button"
|
||||
class="px-3 py-1 text-sm text-gray-700 bg-white border border-gray-300 rounded hover:bg-gray-50"
|
||||
hx-get="/api/admin/archived-records?page={{ $page }}"
|
||||
hx-target="#archived-record-table"
|
||||
hx-include="#filterForm">
|
||||
{{ $page }}
|
||||
</button>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- 다음 페이지 --}}
|
||||
@if($records->hasMorePages())
|
||||
<button type="button"
|
||||
class="px-3 py-1 text-sm text-gray-700 bg-white border border-gray-300 rounded hover:bg-gray-50"
|
||||
hx-get="/api/admin/archived-records?page={{ $records->currentPage() + 1 }}"
|
||||
hx-target="#archived-record-table"
|
||||
hx-include="#filterForm">
|
||||
다음
|
||||
</button>
|
||||
@else
|
||||
<span class="px-3 py-1 text-sm text-gray-400 bg-gray-100 rounded cursor-not-allowed">다음</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
{{-- 페이지네이션 (공용 컴포넌트) --}}
|
||||
@include('partials.pagination', [
|
||||
'paginator' => $records,
|
||||
'target' => '#archived-record-table',
|
||||
'includeForm' => '#filterForm'
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user